Spring 2일차.
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>