카테고리 없음

Spring 2일차.

shchan 2024. 10. 29. 18:07

xml 없이 자바클래스로 설정을 하는 방법해보기

 

플러그인 추가 설정 필요 

web.xml 없이도 class를 통해서 설정정보를 읽을 수 있게해야함.

            <plugin>
            	<groupId>org.apache.maven.plugins</groupId>
            	<artifactId>maven-war-plugin</artifactId>
            	<version>3.4.0</version>
            	<configuration>
            		<failOnMissingWebXml>false</failOnMissingWebXml>
            	</configuration>
            </plugin>

추가

 

config 패키지를 만들어서 WebConfig 클래스 생성 extends AbstractAnnotationConfigDispatcherServletInitializer 상속  

@Override
	protected Class<?>[] getRootConfigClasses() {
		// TODO Auto-generated method stub
		return new Class[] {RootConfig.class};
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		// TODO Auto-generated method stub
		return new Class[] {ServletConfiguration.class};
	}

	@Override
	protected String[] getServletMappings() {
		// TODO Auto-generated method stub
		return new String[] {"/"};
	}

 

ServletConfig는 javax servlet에 이미 존재하는 인터페이스가 있기떄문에 이름을 바꿔야함.

 

인코딩 필터설정

	@Override
	protected Filter[] getServletFilters() {
		// TODO Auto-generated method stub
//		CharacterEncodingFilter encoding = new CharacterEncodingFilter("UTF-8", true);
		CharacterEncodingFilter encoding = new CharacterEncodingFilter();
		encoding.setEncoding("UTF-8");
		encoding.setForceEncoding(true); // 외부로 나가는 데이터 인코딩 여부
				
		return new Filter[] {encoding};
	}

 

사용자 지정 설정이 필요한 경우 ( 파일 업로드 ) 

	@Override
	protected void customizeRegistration(Dynamic registration) {
		// TODO Auto-generated method stub
		super.customizeRegistration(registration);
	}

 

servlet-context  =>  ServletConfiguration를 구현

@EnableWebMvc
@ComponentScan(basePackages = {"com.ezen.spring.controller","com.ezen.spring.service","com.ezen.spring.handler"})
public class ServletConfiguration implements WebMvcConfigurer {

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		// resources 경로 설정 (css, js, img, font ...)
		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
		// 나중에 파일 업로드 경로도 추가 예정 
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		// view를 jsp(jstl 포함)로 어떻게 보여줄지 설정
		// view 경로 설정 
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setPrefix("/WEB-INF/views/");
		viewResolver.setSuffix(".jsp");
		// 화면에 뷰를 구성할 때 jstl에 대한 사용을 하는 jsp를 구성하겠다.
		viewResolver.setViewClass(JstlView.class);
	
		registry.viewResolver(viewResolver);
	
	}
 }

 

Root-context => RootConfig 구현

DB에 관련된 설정.

히카리 CP (커넥션풀) 디펜던시 추가

	<!-- HikariCP -->
		<dependency>
		   <groupId>com.zaxxer</groupId>
		   <artifactId>HikariCP</artifactId>
		   <version>6.0.0</version>
		</dependency>

 

@Configuration 필요

이전 WebConfig, ServletConfiguration 는 상속이나 구현을했기때문에 안에 들어있지만 RootConfig는 없기때문에 필요함.

@EnableTransactionManagement
@MapperScan(basePackages = {"com.ezen.spring.dao"})
@Configuration
public class RootConfig {
	
	// @Autowired : 해당 객체를 스프링 객체로 생성
	@Autowired
	ApplicationContext applicationContext;
	// DB에 관련된 설정 : 사용자가 만든 객체를 스프링이 인지하도록 설정 @Bean
	@Bean
	public DataSource dataSource() {
		HikariConfig hikariConfig = new HikariConfig();
		// com.mysql.cj.jdbc.Driver 대신 log4jdbc 드라이버 사용
		// log4jdbc 드라이버 => DB의 흐름으로 로그로 찍어주는 드라이버. 
		// springdb / springUser / mysql
		hikariConfig.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
		hikariConfig.setJdbcUrl("  jdbc:log4jdbc:mysql://localhost:3306/springdb");
		hikariConfig.setUsername("springUser");
		hikariConfig.setPassword("mysql");
		
		// -- 여기서부터 hikari 필수설정 
		hikariConfig.setMaximumPoolSize(5); // 한번에 설정할 최대 커넥션 개수
		hikariConfig.setMinimumIdle(5); // 최소 유휴 커넥션 수 (max와 같은 개수로 설정)
		
		hikariConfig.setConnectionTestQuery("SELECT now()"); // 첫 연결시 test sql
		hikariConfig.setPoolName("springHikariCP");
		
		// -- 여기서서부터 추가설정
		// cachePrepStmts : cache 사용 여부 설정 
		hikariConfig.addDataSourceProperty("dataSource.cachePrepStmts", "true");
		// mysql 드라이버가 연결당 cache 사이즈 설정 : 250~500사이 권장 
		hikariConfig.addDataSourceProperty("dataSource.prepStmtsCacheSize", "250");
		// connection당 캐싱할 prearedStatment 개수 지정옵션 : default 250
		hikariConfig.addDataSourceProperty("dataSource.prepStmtsCacheSqlLimit", "true");
		// mysql 서버에서 최신 이슈가 있을 경우 지원 받을 것인지 설정
		hikariConfig.addDataSourceProperty("dataSource.useServerPrepStmts", "true");
		
		HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
		
		return hikariDataSource;
		
	}
	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sqlFactoryBean = new SqlSessionFactoryBean();
		sqlFactoryBean.setDataSource(dataSource());
		// 내부 ser/main/resources의 대한 위치값이 필요 
		sqlFactoryBean.setMapperLocations(
				applicationContext.getResources("classpath:/mappers/*.xml"));
		
		// DB : _(스네이크 표기법) / java : 카멜표기법 reg_date( db 스네이크 ) regDate ( java 카멜)
		sqlFactoryBean.setConfigLocation(
				applicationContext.getResource("classpath:/mybatisConfig.xml"));
		
	    return sqlFactoryBean.getObject();		    	
	}
	
	// 트랜젝션 매니저 설정
	@Bean
	public DataSourceTransactionManager transactionManager() {
		return new DataSourceTransactionManager(dataSource());
	}

 

mybatisConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
  <configuration>
   <settings>
 	 <setting name="mapUnderscoreToCamelCase" value="false"/>  
   </settings>   
  </configuration>
   <!-- <typeAliases>
   <typeAlias type="com.ezen.spring.domain.BoardVO" alias="bvo"/>
   </typeAliases> -->

 

typeAliases alias처리를해서 짧게 사용가능. 헷갈리니까 사용하진않을것.

 

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are  by default assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d %-5p - %msg%n</pattern>
    </encoder>
  </appender>
  
  <!-- Application Loggers -->
  <logger name="com.ezen.spring" level="info" appender-ref="STDOUT" />
  <logger name="org.springframework.core" level="info" appender-ref="STDOUT" />
  <logger name="org.springframework.beans" level="info" appender-ref="STDOUT" />
  <logger name="org.springframework.context" level="info" appender-ref="STDOUT" />
  <logger name="org.springframework.web" level="info" appender-ref="STDOUT" />
  <logger name="org.springframework.jdbc" level="info" appender-ref="STDOUT" />
  <logger name="jdbc.sqlonly" level="info" appender-ref="STDOUT" />
  <logger name="jdbc.resultsettable" level="info" appender-ref="STDOUT" />
  
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 

이걸로 설정을 끝내고 

Header Footer를 만들고 보드 게시판구현 (부트스트랩 이용)

 

header.jsp

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-fluid">
    <a class="navbar-brand" href="/">Spring</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="/">index</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link<</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
       </ul>
    </div>
  </div>
</nav>

 

footer.jsp

	<div class="container-md">
		2024 Spring Project... by SH
	</div>

 

처음 나오는 메인화면에다가 jsp:include로 header footer 삽입가능.

<jsp:include page="layout/header.jsp"/>


<div class="container-md">


<P>  The time on the server is ${serverTime}. </P>


</div>

<jsp:include page="layout/footer.jsp"/>

 

header에다가 

 <li class="nav-item">
          <a class="nav-link" href="/board/register">게시판 글쓰기</a>
        </li>

글쓰기 메뉴를 만들고

register.jsp를 구현

	<jsp:include page="../layout/header.jsp" />

	<form action="/board/insert" method="post">
		<div class="container-md">
			<h1>Board Register Page...</h1>
			<div class="mb-3">
				<label for="t" class="form-label">title</label> <input type="text"
					class="form-control" id="t" name="title" placeholder="title...">
			</div>
			<div class="mb-3">
				<label for="w" class="form-label">writerL</label> <input type="text"
					class="form-control" id="w" name="writer" placeholder="writer...">
			</div>
			<div class="mb-3">
				<label for="c" class="form-label">content</label>
				<textarea class="form-control" id="c" rows="3" name="content"></textarea>
			</div>
			<button type="submit" class="btn btn-primary">register</button>
		</div>
	</form>

	<jsp:include page="../layout/footer.jsp" />

헤더 푸터넣고 안에 제목 작성자 내용을 전송을 할 수있게 form태그안에 구성

 

컨트롤러에다가

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/board/*")
@Controller
public class BoardController {
	private final BoardService bsv;

	// return void => 온 경로 그대로 리턴 /board/register => /board/register.jsp 
	@GetMapping("/register")
	public void register() {}

	@PostMapping("/insert")
	public String insert(BoardVO bvo) {
		log.info(">>> insert bvo > {} ", bvo);
		int isOk = bsv.insert(bvo);
		
		log.info(" insert >> {} ", ( isOk >0? "성공" : "실패" ));
		// 컨트롤러의 mapping 위치로 연결할 때 redirect:
		return "redirect:/";
	}

 

여러번 해본 작업이기에 자세한 기재는 생략 저번 글들을 참고. 

register와 insert를 구현하고 다오까지 쭉 연결해서 mapper까지 구현

생성자 주입시 객체는 final로 생성 ( 생성자 주입은 방법이 많음 )

 

앞으로 만드는 list , detail 등의 페이지들은 위아래에 헤더 푸터가 구현돼있지만 생략.

보드 게시판의 리스트를 만들어주기위해 list.jsp 생성 부트스트랩 이용

<div class="container-md">
<table class="table table-hover">
<thead>
		<tr>
			<th scope="col">no.</th>
			<th scope="col">title</th>
			<th scope="col">writer</th>
			<th scope="col">regDate</th>
			<th scope="col">readCount</th>
		</tr>
</thead>
<tbody>
		<c:forEach items="${list }" var="bvo" >
			<tr>
				<td>${bvo.bno }</td>
				<td><a href="/board/detail?bno=${bvo.bno } ">${bvo.title }</a></td>
				<td>${bvo.writer }</td>
				<td>${bvo.regDate }</td>
				<td>${bvo.readCount }</td>
			</tr>
		</c:forEach>
		</tbody>
	</table>	
	<a href="/"><button type="button" class="btn btn-success">index</button></a> 
 </div>

 

header에 게시판 게시글들을 쭉 볼 수있게 보기 메뉴로 수정해주고

   <li class="nav-item">
          <a class="nav-link" href="/board/list">게시판 보기</a>
        </li>

 

Model을 이용해서 addAttribute로 List를 가져와서 보낼 수 있도록함.

	@GetMapping("/list")
	public String list(Model m) {
		//request.setAttrbute()
		//Model 객체가 해당일을 대신해줌
		
		List<BoardVO> list = bsv.getList();
		
		m.addAttribute("list",list);
		
		return "board/list";
		
	}

 

서비스 , 다오 연결하고 매퍼까지구현.

이제 이제 게시글 리스트를 쭉 볼 수 있음. 어제했던 것들을 xml 설정없이 자바클래스로 구성하는 것이기에 설정이 다 끝난 상태에서 구현은 거의 비슷 자세한 것들은 저번글들을 참고.

 


test 해보기 

java build path에서 Junit 추가하고 

test/java 에 BoardTest를 하나만들어서 테스트해보기.

 

final 생성자 주입 사용할 수 없으니까 @Autowired 사용

Test로 돌려보는것이니까 무슨 역할인지 어노테이션으로 @Test 사용

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {com.ezen.spring.config.RootConfig.class})
public class BoardTest {
	
	@Autowired
	private BoardDAO bdao;
	
	@Test
	public void insertBoardDummies() {
		for(int i = 1 ; i <= 5000; i++) {
			BoardVO bvo = new BoardVO();
			bvo.setTitle("Test Title" + i);
			bvo.setWriter("tester" + ((int)(Math.random()*500)+1) + "@tester.com" );
			bvo.setContent("Test Content..." + i);
			
			bdao.insert(bvo);
		}
	}
}

 

반복문을돌려서 5000개의 데이터가 잘 insert되는지 확인 dao를 import 했으므로 메서드는 그냥 가져와서 사용하면됨.

Run as에 Junit Test로 실행해야함. 잘 들어가는지 확인 한 후 5000개는 일단 너무많으니 limit로 10개 끊고

페이지네이션을 이후에 해 볼 예정

 <select id="getList" resultType="com.ezen.spring.domain.BoardVO">
 	select * from board 
 	where is_del = 'N'
 	order by bno desc 
 	limit 0,10
 </select>