Spring & Springboot/올인원 스프링 프레임워크

@Configuration와 BeanPropertyRowMapper

YJ_ma 2023. 12. 4. 17:01

@Configuration

이전 6장에서 스프링 설정 파일을 애너테이션(@Configuration, @Bean)을 이용한 Java 파일로 만들었다. 도서 대출 서비스도 @Configuration과 @Bean을 이용해서 빈을 생성하고 조립해본다.

빈 생성을 위한 xml 파일을 java로 변경

 

JdbcTemplate

데이터베이스 통신에 필요한 JdbcTemplate을 @Configuration과 @Bean을 이용해서 IoC 컨테이너에 빈으로 생성하는 과정이다.

① com.office.library.config 패키지에 JdbcTemplateConfig.java 클래스를 만들고 @Configuration을 명시한다.

JdbcTemplate 빈을 생성하기 위한 코드는 다음과 같다.

package com.office.library.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
public class JdbcTemplateConfig {

	@Value("#{infoProperty['db.driver']}")
	private String dbDriver;

	@Value("#{infoProperty['db.url']}")
	private String dbUrl;

	@Value("#{infoProperty['db.username']}")
	private String dbUsername;

	@Value("#{infoProperty['db.password']}")
	private String dbPassword;

	@Bean
	public DataSource dataSource() {
		System.out.println("[JdbcTemplateConfig] dataSource()");

		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName(dbDriver);
		dataSource.setUrl(dbUrl);
		dataSource.setUsername(dbUsername);
		dataSource.setPassword(dbPassword);

		return dataSource;

	}

	@Bean
	public JdbcTemplate jdbcTemplate() {
		System.out.println("[JdbcTemplateConfig] jdbcTemplate()");

		return  new JdbcTemplate(dataSource());

	}

	@Bean
	public DataSourceTransactionManager transactionManager() {
		System.out.println("[JdbcTemplateConfig] transactionManager()");

		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource());

		return transactionManager;

	}

}

 

jdbc-context.xml 대신하는 JdbcTemplateConfig.java를 만들었으므로 web.xml의 jdbc-context.xml을 주석 처리한다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    /WEB-INF/spring/root-context.xml
    <!-- /WEB-INF/spring/jdbc-context.xml -->
    /WEB-INF/spring/security-context.xml
    /WEB-INF/spring/mail-context.xml
    /WEB-INF/spring/file-context.xml
    /WEB-INF/spring/properties-context.xml
    </param-value>
</context-param>

 

JdbcTemplateConfig.java가 정상 작동하는지 확인하기 위해 프로젝트를 재시작하고 로그인을 시도한다.

 

CommonsMultipartResolver

파일 업로드에 필요한 CommonsMultipartResolver를 @Configuration과 @Bean을 이용해서 IoC 컨테이너에 빈으로 생성해본다.

com.office.library.config 패키지에 MultipartResolverConfig.java 클래스를 만들고 @Configuration을 명시한다.

package com.office.library.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class MultipartResolverConfig {

}

 

CommonsMultipartResolver의 maxUploadSize와 defaultEncoding 초기화 값도 프로퍼티를 이용하기 위해서 [properties] 폴더에 comm.info.properties 파일을 만들고 다음과 같이 코딩한다.

 

uploadfile.size = 10240000
uploadfile.encoding=uft-8

 

comm.info.properties를 properties-context.xml의 <util:properties>에 등록한다.

<util:properties id="commInfoProperty" location="/WEB-INF/spring/properties/comm.info.properties"/>

 

CommonsMultipartResolver 빈을 생성하기 위한 코드를 다음과 같이 작성한다.

package com.office.library.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

@Configuration
public class MultipartResolverConfig {

	@Value("#{commInfoProperty['uploadfile.size']}")
	private long fileSize;

	@Value("#{commInfoProperty['uploadfile.encoding']}")
	private String fileEncoding;

	@Bean
	public CommonsMultipartResolver multipartResolver() {
		System.out.println("[MultipartResolverConfig] multipartResolver()");

		CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
		multipartResolver.setMaxUploadSize(fileSize);
		multipartResolver.setDefaultEncoding(fileEncoding);

		return multipartResolver;

	}

}

 

file-context.xml을 대신하는 MultipartResolverConfig.java를 만들었기 때문에 web.xml의 file-context.xml을 주석 처리한다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    /WEB-INF/spring/root-context.xml
    <!-- /WEB-INF/spring/jdbc-context.xml -->
    /WEB-INF/spring/security-context.xml
    <!--/WEB-INF/spring/file-context.xml -->
    /WEB-INF/spring/mail-context.xml
    /WEB-INF/spring/properties-context.xml
    </param-value>
</context-param>

 

정상 작동하는지 확인하기 위해 프로젝트를 재시작하고 신규 도서를 등록하면서 표지 이미지 파일을 업로드한다.

 

JavaMailSenderImpl

메일 발송에 필요한 JavaMailSenderImpl을 @Configuration과 @Bean을 이용해서 IoC 컨테이너에 빈으로 생성한다.

① com.office.library.config 패키지에 MailSenderConfig.java 클래스를 만들고 @Configuration을 명시한다.

package com.office.library.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class MailSenderConfig {

}

 

② JavaMailSenderImpl의 멤버 필드 초기화 값도 프로퍼티를 이용하기 위해서 comm.info.properties에 다음 내용을 추가한다.

# Mail(find password)
mail.host=smtp.gmail.com
mail.port=587
mail.username=mayoonju@gmail.com
mail.password=qsvfzlaphflciprz
mail.smtp.auth=true
mail.smtp.starttls.enable=true

 

③ JavaMailSenderImpl 빈을 생성하기 위한 코드를 다음과 같이 입력한다.

package com.office.library.config;

import java.util.Properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSenderImpl;

@Configuration
public class MailSenderConfig {

	@Value("#{commInfoProperty['mail.host']}")
	private String mailHost;

	@Value("#{commInfoProperty['mail.port']}")
	private int mailPort;

	@Value("#{commInfoProperty['mail.username']}")
	private String mailUserName;

	@Value("#{commInfoProperty['mail.password']}")
	private String mailPassword;

	@Value("#{commInfoProperty['mail.smtp.auth']}")
	private String mailSmtpAuth;

	@Value("#{commInfoProperty['mail.smtp.starttls.enable']}")
	private String mailSmtpStarttlsEnable;

	@Bean
	public JavaMailSenderImpl mailSender() {
		System.out.println("[MailSenderConfig] mailSender()");

		JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
		mailSender.setHost(mailHost);
		mailSender.setPort(mailPort);
		mailSender.setUsername(mailUserName);
		mailSender.setPassword(mailPassword);

		Properties properties = new Properties();
		properties.setProperty("mail.smtp.auth", mailSmtpAuth);
		properties.setProperty("mail.smtp.starttls.enable", mailSmtpStarttlsEnable);

		mailSender.setJavaMailProperties(properties);

		return mailSender;

	}

}

 

④ web.xml의 mail-context.xml을 주석처리한다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    /WEB-INF/spring/root-context.xml
    <!-- /WEB-INF/spring/jdbc-context.xml -->
    /WEB-INF/spring/security-context.xml
    <!--/WEB-INF/spring/file-context.xml -->
    <!-- /WEB-INF/spring/mail-context.xml -->
    /WEB-INF/spring/properties-context.xml
    </param-value>
</context-param>

 

⑤ 정상 작동하는지 확인하기 위해 프로젝트를 재시작하고 비밀번호를 찾아본다.

 

BCryptPasswordEncoder

비밀번호 암호화에 필요한 BCryptPasswordEncoder를 @Configuration과 @Bean을 이용해서 IoC 컨테이너에 빈으로 생성해본다.

① com.office.library.config 패키지에 SecurityConfig.java 클래스를 만들고 다음과 같이 코딩한다.

package com.office.library.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class SecurityConfig {

	@Bean
	public BCryptPasswordEncoder bCryptPasswordEncoder() {
		System.out.println("[SecurityConfig] bCryptPasswordEncoder()");

		return new BCryptPasswordEncoder();
	}
}

 

② bCryptPasswordEncoder() : BCryptPasswordEncoder를 생성해서 반환한다.

security-context.xml을 대신하기 때문에 web.xml에서 주석처리를 해준다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    /WEB-INF/spring/root-context.xml
    <!-- /WEB-INF/spring/jdbc-context.xml -->
    <!-- /WEB-INF/spring/security-context.xml -->
    <!--/WEB-INF/spring/file-context.xml -->
    <!-- /WEB-INF/spring/mail-context.xml -->
    /WEB-INF/spring/properties-context.xml
    </param-value>
</context-param>

 

③ 정상적으로 작동하는지 확인하기 위해 프로젝트를 실행하고 새로운 아이디로 회원가입하여 비밀번호가 암호화되는지 확인한다.

 

 

BeanPropertyRowMapper

우리는 지금까지 DAO에서 JdbcTemplate을 이용해서 데이터를 조회하고, 조회한 데이터(ResultSet)를 RowMapper를 이용해서 VO로 변환했다. 다음은 AdminMemberDao에서 관리자 로그인 인증에 사용한 selectAdmin()의 일부이다.

..생략..
public AdminMemberVo selectAdmin(AdminMemberVo adminMemberVo) {
    System.out.println("[AdminMemberDao] selectAdmin()");

    String sql = "SELECT * FROM tbl_admin_member " 
            + "WHERE a_m_id = ? AND a_m_approval > 0";

    List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();

    try {
        adminMemberVos = jdbcTemplate.query(sql,  new RowMapper<AdminMemberVo>() {
            @Override
            public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
                AdminMemberVo adminMemberVo = new AdminMemberVo();

                adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
                adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
                adminMemberVo.setA_m_id(rs.getString("a_m_id"));
                adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
                adminMemberVo.setA_m_name(rs.getString("a_m_name"));
                adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
                adminMemberVo.setA_m_part(rs.getString("a_m_part"));
                adminMemberVo.setA_m_position(rs.getString("a_m_position"));
                adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
                adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
                adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
                adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));

                return adminMemberVo;
            }
        }, adminMemberVo.getA_m_id());

        if (!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw()))
            adminMemberVos.clear();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}

 

mapRow()는 ResultSet을 VO로 변환해주는 편리한 메서드이지만 어딘가 불편한 점이 있다. 개발자는 매번 setter 메서드를 이용해서 ResultSet의 데이터를 VO의 멤버 필드에 저장해야 한다는 것이다.

이러한 반복 작업은 코드가 불필요하게 길어지도록 만든다. 이를 해결하고자 스프링에서는 BeanPropertyRowMapper를 제공한다.

 

다음은 AdminMemberDao.java의 기존코드를 BeanPropertyRowMapper를 이용한 selectAdmin()함수이다.

public AdminMemberVo selectAdmin(AdminMemberVo adminMemberVo) {
    System.out.println("[AdminMemberDao] selectAdmin()");

    String sql = "SELECT * FROM tbl_admin_member " 
            + "WHERE a_m_id = ? AND a_m_approval > 0";

    List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();

    try {
        //			adminMemberVos = jdbcTemplate.query(sql,  new RowMapper<AdminMemberVo>() {
        //				@Override
        //				public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
        //					AdminMemberVo adminMemberVo = new AdminMemberVo();
        //
        //					adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
        //					adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
        //					adminMemberVo.setA_m_id(rs.getString("a_m_id"));
        //					adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
        //					adminMemberVo.setA_m_name(rs.getString("a_m_name"));
        //					adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
        //					adminMemberVo.setA_m_part(rs.getString("a_m_part"));
        //					adminMemberVo.setA_m_position(rs.getString("a_m_position"));
        //					adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
        //					adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
        //					adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
        //					adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));
        //
        //					return adminMemberVo;
        //				}
        //			}, adminMemberVo.getA_m_id());

        RowMapper<AdminMemberVo> rowMapper = BeanPropertyRowMapper.newInstance(AdminMemberVo.class);
        adminMemberVos = jdbcTemplate.query(sql, rowMapper, adminMemberVo.getA_m_id());

        if (!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw()))
            adminMemberVos.clear();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}

 

AdminMemberDao에는 selectAdmin()이 3개 있고 selectAdmins()가 1개 있다. 이를 모두 BeanPropertyRowMapper를 이용한 코드로 변경해본다. 

public List<AdminMemberVo> selectAdmins(){
    System.out.println("[AdminMemberDao] selectAdmins()");

    String sql = "SELECT * FROM tbl_admin_member";

    List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();

    try {
        //			adminMemberVos = jdbcTemplate.query(sql, new RowMapper<AdminMemberVo>() {
        //				@Override
        //				public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException{
        //					AdminMemberVo adminMemberVo = new AdminMemberVo();
        //
        //					adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
        //					adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
        //					adminMemberVo.setA_m_id(rs.getString("a_m_id"));
        //					adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
        //					adminMemberVo.setA_m_name(rs.getString("a_m_name"));
        //					adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
        //					adminMemberVo.setA_m_part(rs.getString("a_m_part"));
        //					adminMemberVo.setA_m_position(rs.getString("a_m_position"));
        //					adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
        //					adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
        //					adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
        //					adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));
        //
        //					return adminMemberVo;
        //				}
        //			});

        RowMapper<AdminMemberVo> rowMapper = BeanPropertyRowMapper.newInstance(AdminMemberVo.class);
        adminMemberVos = jdbcTemplate.query(sql, rowMapper);

    } catch (Exception e) {
        e.printStackTrace();
    }
    return adminMemberVos;
}
    
public AdminMemberVo selectAdmin(int a_m_no) {
    System.out.println("[AdminMemberDao] selectAdmin()");

    String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_no = ?";

    List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();

    try {
        //			adminMemberVos = jdbcTemplate.query(sql, new RowMapper<AdminMemberVo>() {
        //
        //				@Override
        //				public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
        //					AdminMemberVo adminMemberVo = new AdminMemberVo();
        //
        //					adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
        //					adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
        //					adminMemberVo.setA_m_id(rs.getString("a_m_id"));
        //					adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
        //					adminMemberVo.setA_m_name(rs.getString("a_m_name"));
        //					adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
        //					adminMemberVo.setA_m_part(rs.getString("a_m_part"));
        //					adminMemberVo.setA_m_position(rs.getString("a_m_position"));
        //					adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
        //					adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
        //					adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
        //					adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));
        //
        //					return adminMemberVo;
        //				}
        //			}, a_m_no);

        RowMapper<AdminMemberVo> rowMapper = BeanPropertyRowMapper.newInstance(AdminMemberVo.class);
        adminMemberVos = jdbcTemplate.query(sql, rowMapper, a_m_no);

    } catch (Exception e) {
        e.printStackTrace();
    }
    return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}

public AdminMemberVo selectAdmin(String a_m_id, String a_m_name, String a_m_mail) {
    System.out.println("[AdminMemberDao] selectAdmin()");

    String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_id = ? AND a_m_name = ? AND a_m_mail = ?";

    List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();

    try {
        //			adminMemberVos = jdbcTemplate.query(sql, new RowMapper<AdminMemberVo>() {
        //
        //				@Override
        //				public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
        //					AdminMemberVo adminMemberVo = new AdminMemberVo();
        //
        //					adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
        //					adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
        //					adminMemberVo.setA_m_id(rs.getString("a_m_id"));
        //					adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
        //					adminMemberVo.setA_m_name(rs.getString("a_m_name"));
        //					adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
        //					adminMemberVo.setA_m_part(rs.getString("a_m_part"));
        //					adminMemberVo.setA_m_position(rs.getString("a_m_position"));
        //					adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
        //					adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
        //					adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
        //					adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));
        //
        //					return adminMemberVo;
        //				}
        //			}, a_m_id, a_m_name, a_m_mail);

        RowMapper<AdminMemberVo> rowMapper = BeanPropertyRowMapper.newInstance(AdminMemberVo.class);
        adminMemberVos = jdbcTemplate.query(sql, rowMapper, a_m_id, a_m_name, a_m_mail);

    } catch (Exception e) {
        e.printStackTrace();
    }
    return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}