설정이 중요하다.
설정이 끝나면 그다음부터 쉽다.
설정까지 가는 과정을 정확하게 알고있어야한다.
<<ServletContextLoaderListener>>ContextLoaderListener
ContextLoaderListener
웹 어플리케이션이 시작될때 여러 프론트컨트롤러가 공통으로 사용할 객체를 준비한다.
ContextLoaderListener
는 Spring IoC 컨테이너를 소유하고있고,
Spring IoC 컨테이너
는 RootConfig를 참고해서 객체를 준비
RootConfig는 DataSource, TransactionManger, DAO, Servie 객체를 설정한다.
:: ContextLoaderListener 객체생성은 -> ?
:: Spring IoC 컨테이너 객체생성은 -> ?
:: RootConfig를 생성하면
웹어플리케이션이 시작되면, ServletContextListener보다 SpringServletContainerInitializer먼저 시작되고 WebApplicationInitializer를 찾아 OnStarter()를 호출한다.
1) Servlet 컨테이너 시작
2) 웹 어플리케이션을 시작
3) SpringServletContainerInitializer.onStartup()
4) WebApplicationInitializer 인터페이스의 규칙에 따라서 만든 구현체의 onStartup()가 호출된다.
서블릿컨테이너에 등록되어있는 서블릿들중에서 프론트컨틀롤러의 서비스를 호출되는데 그 URL에맞는 페이지 컨트롤러를 호출하는데.. 그럼 Serivce, DAO,
package com.bitcamp.board.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
@ComponentScan(value = "com.bitcamp.board",
excludeFilters = @Filter(type = FilterType.REGEX, pattern = "com.bitcamp.board.controller.*"))
// com.bitcamp.board 패키지를 다 찾아서 애노테이션이 붙은 클래스찾아 객체를 생성하는데,
// "com.bitcamp.board.controller.*" 패키지는 제외한다.
// type = FilterType.REGEX : 정규표현식
// "com.bitcamp.board.controller.*"를 패턴으로 만드려면 타입을 FilterType.REGEX로 정해줘야 한다.
public class RootConfig {
public RootConfig() {
System.out.println("RootConfig() 생성자 호출됨!");
}
}
package com.bitcamp.board.config;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
public class DatabaseConfig {
public DatabaseConfig() {
System.out.println("DatabaseConfig() 생성자 호출됨!");
}
// 객체가 리턴한 값을 transactionManager라는 이름으로 저장한다.
// @Bean 애노테이션을 붙일 때 객체 이름을 지정하면
// 그 이름으로 리턴 값을 컨테이너에 보관한다.
// 이름을 지정하지 않으면 메서드 이름으로 보관한다.
@Bean
public PlatformTransactionManager transactionManager(DataSource ds) {
// Spring IoC 컨테이너는 이 메서드를 호출하기 전에
// 이 메서드가 원하는 파라미터 값인 DataSource를 먼저 생성한다.
// => createDataSource() 메서드를 먼저 호출한다.
System.out.println("createTransactionManager() 호출됨!");
return new DataSourceTransactionManager(ds);
}
// DataSource를 생성하는 메서드를 호출하라고 표시한다.
// 메서드가 리턴한 객체는 @Bean 애노테이션에 지정된 이름으로 컨테이너에 보관될 것이다.
@Bean
public DataSource dataSource() {
System.out.println("DataSource 객체 생성!");
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.mariadb.jdbc.Driver");
ds.setUrl("jdbc:mariadb://localhost:3306/studydb");
ds.setUsername("study");
ds.setPassword("1111");
return ds;
}
}
package com.bitcamp.board.config;
import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.bitcamp.board.filter.LoginCheckFilter;
public class AppWebApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {RootConfig.class, DatabaseConfig.class};
}
@Override
protected String getServletName() {
return "app";
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {AppWebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app/*"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[] {new CharacterEncodingFilter("UTF-8"), new LoginCheckFilter()};
}
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement(System.getProperty("java.io.tmpdir"), // 클라이언트가 보낸 파일을 임시 저장할 때 사용할 디렉토리
1024 * 1024 * 5, // 한 파일의 최대 크기
1024 * 1024 * 10, // 첨부 파일을 포함한 전체 요청 데이터의 최대 크기
1024 * 1024 // 첨부 파일을 데이터를 일시적으로 보관할 버퍼 크기
));
}
}
RootConfig는 DAO, Service 설정
DatabaseConfig는 DataSource, TransactionManger 설정
(/app/*) DispatcherServlet은 Spring IoC 컨테이너를 소유하고 있고,
Spring IoC 컨테이너는 AppWebConfig를 참고한다.
AppWebConfig는 Controller, 기타 웹 관련 컴포넌트를 생성시킨다.
(/admin/*) DispatcherServlet AdminWebConfig
package com.bitcamp.board.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@ComponentScan(value = "com.bitcamp.board.controller",
excludeFilters = @Filter(type = FilterType.REGEX,
pattern = "com.bitcamp.board.controller.admin.*"))
public class AppWebConfig {
public AppWebConfig() {
System.out.println("AppWebConfig() 생성자 호출됨!");
}
// multipart/form-data 형식으로 보내온 요청 데이터를
// 도메인 객체로 받는 일을 할 도우미 객체를 등록한다.
// 이 객체가 등록된 경우 multipart/form-data를 도메인 객체로 받을 수 있다.
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
// Spring WebMVC의 기본 ViewResolver를 교체한다.
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class); // 주어진 URL을 처리할 객체 => JSP 실행시킨다.
viewResolver.setPrefix("/WEB-INF/jsp/"); //
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
package com.bitcamp.board.config;
import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.bitcamp.board.filter.LoginCheckFilter;
public class AppWebApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {RootConfig.class, DatabaseConfig.class};
}
@Override
protected String getServletName() {
return "app";
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {AppWebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app/*"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[] {new CharacterEncodingFilter("UTF-8"), new LoginCheckFilter()};
}
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement(System.getProperty("java.io.tmpdir"), // 클라이언트가 보낸 파일을 임시 저장할 때 사용할 디렉토리
1024 * 1024 * 5, // 한 파일의 최대 크기
1024 * 1024 * 10, // 첨부 파일을 포함한 전체 요청 데이터의 최대 크기
1024 * 1024 // 첨부 파일을 데이터를 일시적으로 보관할 버퍼 크기
));
}
}
package com.bitcamp.board.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@ComponentScan(value = "com.bitcamp.board.controller.admin")
public class AdminAppWebConfig {
public AdminAppWebConfig() {
System.out.println("adminAppWebConfig() 생성자 호출됨!");
}
// Spring WebMVC의 기본 ViewResolver를 교체한다.
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class); // 주어진 URL을 처리할 객체 => JSP 실행시킨다.
viewResolver.setPrefix("/WEB-INF/jsp/"); //
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
package com.bitcamp.board.config;
import javax.servlet.Filter;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.bitcamp.board.filter.AdminCheckFilter;
import com.bitcamp.board.filter.LoginCheckFilter;
public class AdminWebApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null; // Root IoC의 설정은 다른 쪽에서 했기때문에 여기서 설정하지 않음.
// Root IoC의 설정 공유함.
}
@Override
protected String getServletName() {
return "admin";
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {AdminAppWebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/admin/*"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[] {new CharacterEncodingFilter("UTF-8"), new LoginCheckFilter(),
new AdminCheckFilter()};
}
}
<li><a href="${contextPath}/admin/member/list">회원</a></li>
반복적이고, 단순한 JDBC 프로그래밍을 제거한다.
SQL을 별도 파일로 분리 -> SQL을 다루기가 더 쉽다.
도입 전
BoardDao
JDBC Code + SQL
도입 후
BoardDao
MyBatis(JDBC Code를 사용하기 쉽게 캡슐화를 시킨것)
캡슐화 : 메서드와 클래스로 묶는다.
SQL 코드(자바 소스파일에서 SQL 코드를 분리)
// SQL Mapper 라이브러리
implementation 'org.mybatis:mybatis:3.5.11'
// Mybatis를 스프링 프레임워크에서 관리할 수 있도록 도와주는 클래스들
implementation 'org.mybatis:mybatis-spring:2.0.7'
package com.bitcamp.board.config;
import javax.sql.DataSource;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
public class MybatisConfig {
public MybatisConfig() {
System.out.println("MybatisConfig() 생성자 호출됨!");
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds, ApplicationContext iocContainer)
throws Exception {
System.out.println("SqlSessionFactory() 생성자 호출됨!");
// Mybatis의 Log4j2 기능 활성화 시키기
LogFactory.useLog4J2Logging();
//SqlSessionFactory를 만들어 줄 객체를 준비한다.
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// Mybatis가 사용할 DB 커넥션풀을 설정한다.
factoryBean.setDataSource(ds);
// Mybatis가 실행할 SQL 문이 들어있는 파일의 위치를 설정한다.
factoryBean.setMapperLocations(
iocContainer.getResources("classpath:com/bitcamp/board/mapper/*Mapper.xml"));
// classpath: ->
return factoryBean.getObject();
}
}
public class AppWebApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {RootConfig.class, DatabaseConfig.class, MybatisConfig.class};
}
sqlSession.selectList("MemberDao.findAll")
"MemberDao"는 MemberDaoMapper.xml의 mapper namespace를 가르킨다.
"findAll"은 select id="findAll"을 가르킨다.
sqlSession.selectList
가 리턴하는 값은 List<Member>
List타입의 Member를 리턴한다.
1개의 레코드값(row)를 1개의 Member 인스턴스에 저장한다.
Member 인스턴스에 저장할때 no컬럼의 값을 setNo()을 호출하여 저장한다.
컬럼의 이름과 Member 인스턴스의 이름이 같아야 한다.(프로퍼티와 같아야한다.)
컬럼 이름에 해당하는 프로퍼티에 값을 담기 때문에 컬럼에 별명을 부여해서 일치 시켜야한다.
no | name | createDate | |
---|---|---|---|
100 | a | a@test.com | 2021-10-18 |
200 | b | b@test.com | 2021-10-18 |
300 | c | c@test.com | 2021-10-18 |
MemberDaoMapper.xml
<mapper namespace="MemberDao">
<select id="findAll" resultType="com.bitcamp.board.domain.Member">
select
mno as no,
name
email,
cdt as createDate
from
app_member
order by
name
</select>
sqlSession.selectOne("MemberDao.findByEmailPassword")
<mapper namespace="MemberDao">
<select id="findByEmailPassword" resultType="com.bitcamp.board.domain.Member">
select
mno as no,
name
email,
cdt as createDate
from
app_member
where
email=#{email} and
pwd=sha2(#{password},256)
</select>
no | name | createDate | |
---|---|---|---|
100 | a | a@test.com | 2022-10-18 |
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("",email);
paramMap.put("",password);
key | value |
---|---|
a | |
password | 1111 |
sqlSession.selectOne("MemberDao.findByEmailPassword", paramMap)
sqlSession.selectOne
는 null 또는 Member 객체를 리턴한다.
null: select 결과가 없을때 null 리턴
Member 객체 : select 결과가 한개라면 Member 객체를 리턴
예외 : select 결과가 여러개라면 예외가 발생
paramMap.get("email") --> email=#{email}
paramMap.get("password") --> email=#{password}
sqlSession.selectOne("MemberDao.findByNo")
int no값을 넣으면 자바에서 auto-boxing기능을 이용해 Integer.valueOf(no) 넣어준다.
sqlSession.selectOne("MemberDao.findByNo", Integer.valueOf(no));
sqlSession.selectOne("MemberDao.findByNo", no);
<mapper namespace="MemberDao">
<select id="findByEmailPassword" resultType="com.bitcamp.board.domain.Member">
select
mno as no,
name
email,
cdt as createDate
from
app_member
where
mno=#{value}
</select>
mno=#{value} 메서드를 통해 넘어올 값이 Wrapper 객체거나 String 객체일 경우 이름으 아무거나 써도 상관없다.
sqlSession.insert
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.bitcamp.board.domain.Member;
@Repository
public class MybatisMemberDao implements MemberDao {
@Autowired
DataSource ds;
@Autowired
SqlSessionFactory sqlSessionFactory;
@Override
public int insert(Member member) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
return sqlSession.insert("MemberDao.insert", member);
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="MemberDao">
<insert id="insert"
parameterType="com.bitcamp.board.domain.Member">
insert into app_member(name,email,pwd)
values(#{name},#{email},sha2(#{password},256))
</insert>
@Override
public Member findByNo(int no) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
return sqlSession.selectOne("MemberDao.findByNo", no);
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="MemberDao">
<select id="findByNo" resultType="com.bitcamp.board.domain.Member">
select
mno no,
name,
email,
cdt createDate
from
app_member
where
mno=#{no}
</select>
@Override
public List<Member> findAll() throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
return sqlSession.selectList("MemberDao.findAll");
}
}
<select id="findAll" resultType="com.bitcamp.board.domain.Member">
select
mno no,
name,
email
from
app_member
order by
name
</select>
@Override
public Member findByEmailPassword(String email, String password) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("email", email);
paramMap.put("password", password);
return sqlSession.selectOne("MemberDao.findByEmailPassword", // SQL 문의 ID
paramMap // SQL 문의 in-parameter(#{})에 들어 갈 값을 담고 있는 객체
);
}
}
<select id="findByEmailPassword" resultType="com.bitcamp.board.domain.Member">
select
mno no,
name,
email,
cdt createDate
from
app_member
where
email=#{email} and pwd=sha2(#{password},256)
</select>
</mapper>
@Override
public int update(Member member) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
return sqlSession.update("MemberDao.update", member);
}
}
<update id="update" parameterType="com.bitcamp.board.domain.Member">
update
app_member
set
name=#{name},
email=#{email},
pwd=sha2(#{password},256)
where
mno=#{no}
</update>
@Override
public int delete(int no) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
return sqlSession.delete("MemberDao.delete", no);
}
}
<delete id="delete">
delete
from
app_member
where
mno=#{value}
</delete>
MariaDB [studydb]> select
-> b.bno,
-> b.title,
-> b.cdt,
-> b.vw_cnt,
-> m.mno,
-> m.name
-> from
-> app_board b
-> join app_member m on b.mno = m.mno
-> order by bno desc;
+-----+---------------+---------------------+--------+-----+-------+
| bno | title | cdt | vw_cnt | mno | name |
+-----+---------------+---------------------+--------+-----+-------+
| 82 | yyy | 2022-10-18 17:12:25 | 0 | 1 | user1 |
| 81 | yyy | 2022-10-18 17:10:34 | 0 | 1 | user1 |
| 80 | asdfa | 2022-10-18 17:08:25 | 0 | 1 | user1 |
| 78 | yyyy | 2022-10-18 17:00:40 | 0 | 6 | admin |
| 75 | test | 2022-10-18 16:26:43 | 0 | 6 | admin |
| 74 | test | 2022-10-18 16:26:36 | 0 | 6 | admin |
| 73 | asdfasdf | 2022-10-18 14:56:49 | 0 | 6 | admin |
| 70 | asdfasdf | 2022-10-14 15:39:15 | 0 | 6 | admin |
| 67 | asetaset | 2022-10-14 14:23:25 | 0 | 6 | admin |
| 35 | test | 2022-10-06 11:22:59 | 0 | 3 | user3 |
| 33 | TEST1 | 2022-10-05 17:45:49 | 0 | 6 | admin |
| 32 | TEST | 2022-10-05 17:45:15 | 0 | 6 | admin |
| 31 | bbbbcc | 2022-10-05 12:36:04 | 0 | 1 | user1 |
| 25 | test1asdfasdf | 2022-10-04 14:44:12 | 0 | 1 | user1 |
| 22 | asdfasdf | 2022-10-04 11:01:02 | 0 | 1 | user1 |
| 21 | asdfasdf | 2022-10-04 11:00:19 | 0 | 1 | user1 |
| 20 | | 2022-10-04 10:59:31 | 0 | 1 | user1 |
| 19 | | 2022-10-04 10:59:08 | 0 | 1 | user1 |
| 18 | test1 | 2022-10-04 10:28:19 | 0 | 1 | user1 |
| 17 | asdfas | 2022-10-03 17:05:53 | 0 | 1 | user1 |
| 16 | 제목6 | 2022-10-02 15:45:23 | 0 | 4 | user4 |
| 15 | 제목5 | 2022-10-02 15:45:23 | 0 | 2 | user2 |
| 14 | 제목4 | 2022-10-02 15:45:23 | 0 | 2 | user2 |
| 13 | 제목3 | 2022-10-02 15:45:23 | 0 | 1 | user1 |
| 12 | 제목2 | 2022-10-02 15:45:23 | 0 | 1 | user1 |
| 11 | 제목1 | 2022-10-02 15:45:23 | 0 | 1 | user1 |
+-----+---------------+---------------------+--------+-----+-------+
26 rows in set (0.003 sec)
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mariadb.jdbc.Statement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.bitcamp.board.domain.AttachedFile;
import com.bitcamp.board.domain.Board;
@Repository
public class MybatisBoardDao implements BoardDao {
@Autowired
DataSource ds;
@Autowired
SqlSessionFactory sqlSessionFactory;
@Override
public Board findByNo1(int no) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
Board board = sqlSession.selectOne("BoardDao.findByNo", no);
// 게시글 첨부파일 가져오기
List<AttachedFile> attachedFiles =
sqlSession.selectList("BoardDao.findAttachedFilesByBoard", no);
board.setAttachedFiles(attachedFiles);
return board;
}
}
DefaultBoardService
@Override
public Board get(int no) throws Exception {
// 방법1:
return boardDao.findByNo(no);
}
<mapper namespace="BoardDao">
<resultMap type="board" id="boardMap">
<id column="bno" property="no"/>
<result column="title" property="title"/>
<result column="cont" property="content"/>
<result column="cdt" property="createdDate"/>
<result column="vw_cnt" property="viewCount"/>
<association property="writer" javaType="member">
<id column="mno" property="no"/>
<result column="name" property="name"/>
</association>
</resultMap>
<resultMap type="AttachedFile" id="attachedFileMap">
<id column="bfno" property="no" />
<result column="filepath" property="filepath" />
<result column="bno" property="boardNo" />
</resultMap>
<select id="findByNo" resultMap="boardMap">
select
b.bno,
b.title,
b.cont,
b.cdt,
b.vw_cnt,
m.mno,
m.name
from
app_board b
join app_member m on b.mno = m.mno
where
b.bno=#{value}
</select>
<select id="findAttachedFilesByBoard" resultMap="attachedFileMap">
select
bfno,
filepath,
bno
from
app_board_file
where
bno =#{value}
</select>
MybatisBoardDao
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mariadb.jdbc.Statement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.bitcamp.board.domain.AttachedFile;
import com.bitcamp.board.domain.Board;
@Repository
public class MybatisBoardDao implements BoardDao {
@Autowired
DataSource ds;
@Autowired
SqlSessionFactory sqlSessionFactory;
// 게시글 상세 정보 가져오기 : 첨부파일 목록 포함 방법2
@Override
public Board findByNo2(int no) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
return sqlSession.selectOne("BoardDao.findByNo", no);
}
}
DefaultBoardService
@Override
public Board get(int no) throws Exception {
// 방법2:
Board board = boardDao.findByNo2(no);
List<AttachedFile> attachedFiles = boardDao.findFilesByBoard(no);
board.setAttachedFiles(attachedFiles);
return board;
}
<select id="findByNo3" resultMap="boardMap">
select
b.bno,
b.title,
b.cont,
b.cdt,
b.vw_cnt,
m.mno,
m.name,
bf.bfno,
bf.filepath
from
app_board b
join app_member m on b.mno = m.mno
left outer join app_board_file bf on b.bno = bf.bno
where
b.bno=#{value}
</select>
MariaDB [studydb]> select
-> b.bno,
-> b.title,
-> b.cont,
-> b.cdt,
-> b.vw_cnt,
-> m.mno,
-> m.name,
-> bf.bfno,
-> bf.filepath
-> from
-> app_board b
-> join app_member m on b.mno = m.mno
-> left outer join app_board_file bf on b.bno = bf.bno
-> where
-> b.bno=67;
+-----+----------+----------+---------------------+--------+-----+-------+------+----------+
| bno | title | cont | cdt | vw_cnt | mno | name | bfno | filepath |
+-----+----------+----------+---------------------+--------+-----+-------+------+----------+
| 67 | asetaset | asetaset | 2022-10-14 14:23:25 | 0 | 6 | admin | NULL | NULL |
+-----+----------+----------+---------------------+--------+-----+-------+------+----------+
1 row in set (0.001 sec)
// 게시글 상세 정보 가져오기 : 첨부파일 목록 포함 방법3
@Override
public Board findByNo3(int no) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
return sqlSession.selectOne("BoardDao.findByNo3", no);
}
}