Spring 강의 day 8

주세환·2023년 5월 9일
0

Spring

목록 보기
8/18
# spring.datasource.url=jdbc:h2:tcp://1.234.5.158:31521/ds201;Mode=Oracle
spring.datasource.url=jdbc:h2:tcp://1.234.5.158:31521/ds201;Mode=Mysql

application.properties에서 Oracle을 Mysql로 변경한다.

변경하여도 정상적으로 잘 작동한다.


Address

테이블 생성

@Data
@Entity
@Table(name = "ADDRESS1")
@SequenceGenerator(name = "SEQ_ADDRESS1_NO", sequenceName = "SEQ_ADDRESS1_NO", initialValue = 1)
public class Address1 {
    
    // 주소번호, 기본키, 시퀀스
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_A1")
    @Column(name = "NO")
    private long no;

    // 우편번호
    @Column(name = "POSTCODE", length = 10)
    private String postcode;

    // 주소(생략시 컬럼명 변수명과 같고 길이는 255)
    private String address;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @UpdateTimestamp // 변경시에도 날짜 정보 변경, CreationTimestamp => 추가할 때만 날짜 정보 저장
    private Date regdate;
    
    // 외래키 (생성되는 컬럼은 MEMID 래퍼런스컬럼은 MEMBER1 테이블의 ID)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMID", referencedColumnName = "ID" )
    private Member1 member1;
}

entity에 Address1.java를 생성하여 테이블을 생성한다.


@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "MEMBER1") // 생성하고자 하는 테이블, 또는 생성되어 있는 테이블 매칭
public class Member1 {
    
    @Id //import javax.persistence.Id;
    @Column(name="ID", length = 50)
    private String id; // @Column을 생략하면 변수명이 컬럼명

    private String pw;

    private String name;

    private int age;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @CreationTimestamp // 추가시에만 날짜 정보 저장
    private Date regdate;
    
	// EAGER => member1 조회시 address1을 조인하여 보여줌
    // LAZY  => member1 조회시 address1을 조인하지 않고 address1을 필요할 때 조인함
    // cascade => member1의 회원을 지우면 자동으로 address1의 관련 주소도 삭제함
    @OneToMany(mappedBy = "member1", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    @OrderBy(value = "no desc")
    List<Address1> list = new ArrayList<>();
}

연결하는 Member1을 위처럼 수정한다.


spring.jpa.hibernate.ddl-auto=update

application.properties에서 update로 변경 후 서버를 구동하여 테이블을 생성한다.

DB에 테이블과 시퀀스가 정상적으로 추가되었다.

그리고 다시

spring.jpa.hibernate.ddl-auto=validate

application.application에서 바꾼 update를 다시 validate로 변경한다.


주소관리

<body>
    <h3>회원목록(member1)</h3>
    <a th:href="@{/member1/join.do}"><button>회원가입</button></a>

    <form th:action="@{/member1/selectlist.do}" method="get">
        <input type="text" name="text" placeholder="검색어" />
        <input type="hidden" name="page" value="1" />
        <input type="submit" value="검색" />
    </form>

    <table border="1">
        <tbody>
            <tr th:each="obj : ${list}">
                <td th:text="${obj.id}"></td>
                <td th:text="${obj.pw}"></td>
                <td th:text="${obj.name}"></td>
                <td th:text="${obj.age}"></td>
                <td th:text="${obj.regdate}"></td>
                <td>
                    <a th:href="@{/member1/update.do(id=${obj.id})}"><button>수정</button></a> 
                    <form th:action="@{/member1/delete.do}" method="post" style="display:inline-block;">
                        <input type="hidden" name="id" th:value="${obj.id}" />
                        <input type="submit" value="삭제" />
                    </form>
                    <a th:href="@{/address1/selectlist.do(id=${obj.id})}"><button>주소관리</button></a>
                </td>
            </tr>
        </tbody>
    </table>

    <th:block th:each="num : ${#numbers.sequence(1, pages)}">
        <a th:href="@{/member1/selectlist.do(text=${param.text}, page=${num})}" th:text="${num}"></a>
    </th:block>
</body>

member1의 selectlist.html을 위처럼 수정한다.

위 사진처럼 주소관리 버튼이 생성된다.


@Controller
@RequestMapping(value = "/address1")
@Slf4j
@RequiredArgsConstructor
public class Address1Controller {
    
    final String format = "Address1 => {}";

    // address1/selectlist.do?id=b
    @GetMapping(value = "/selectlist.do")
    public String selectListGET(@RequestParam(name = "id") String id ) {
        try{
            log.info(format, id.toString());
            
            // redirect 없을 때는 html 표시
            return "/address1/selectlist";
        }
        catch(Exception e){
            e.printStackTrace();
            // redirect: 주소창의 주소 바꿈
            return "redirect:/home.do";
        }
    }
}

Address1Controller.java를 생성한다.


<body>
    <h3>주소목록(address1)</h3>
    <a th:href="@{/member1/selectlist.do}"><button>회원목록</button></a>
    <hr />

</body>

templates - address1 - selectlist.html을 생성한다.

회원목록에서 주소관리를 누르면 주소목록창으로 이동한다.


@Controller
@RequestMapping(value = "/address1")
@Slf4j
@RequiredArgsConstructor
public class Address1Controller {
    
    final Member1Repository m1Repository; // 저장소객체
    final String format = "Address1 => {}";


    // address1/selectlist.do?id=b
    @GetMapping(value = "/selectlist.do")
    public String selectListGET( Model model,
        @RequestParam(name = "id") String id ) {
        try{
            Member1 member1 = m1Repository.findById(id).orElse(null);
            log.info(format, member1.toString());

            model.addAttribute("obj", member1);

            // redirect 없을 때는 html 표시
            return "/address1/selectlist";
        }
        catch(Exception e){
            e.printStackTrace();
            // redirect: 주소창의 주소 바꿈
            return "redirect:/home.do";
        }
    }
}

Address1Controller.java를 위처럼 수정하고

<body>
    <h3>주소목록(address1)</h3>
    <a th:href="@{/member1/selectlist.do}"><button>회원목록</button></a>
    <hr />
    회원아이디 : <label th:text="${obj.id}"></label><br />
    회원이름 : <label th:text="${obj.name}"></label><br />
    <hr />

    주소목록 : 
    <table border="1">
        <thead>
            <tr>
                <th>주소번호</th>
                <th>우편번호</th>
                <th>주소</th>
                <th>날짜</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="tmp : ${obj.list}">
                <td th:text="${tmp.no}"></td>
                <td th:text="${tmp.postcode}"></td>
                <td th:text="${tmp.address}"></td>
                <td th:text="${tmp.regdate}"></td>
            </tr>
        </tbody>
    </table>
    <hr />
    
    주소등록 : 
    <form th:action="@{/address1/insert.do}" method="post">
        <input type="text" name="member1.id" th:value="${obj.id}" readonly /><br />
        <input type="text" name="postcode" placeholder="우편번호" /><br />
        <input type="text" name="address" placeholder="주소" /><br />
        <input type="submit" value="주소등록" /><br />
    </form>
</body>

selectlist.html을 위처럼 수정한다.


[사진]


저장소 생성

// 저장소 생성 JpaRepository에는 기본적인 crud구현이 되어있음
@Repository
public interface Address1Repository extends JpaRepository<Address1, Long>{   
}

repository 폴더에 Address1Repository.java를 생성한다,


주소 등록

final Address1Repository a1Repository; // 저장소객체

...

@PostMapping(value = "/insert.do")
public String insertPOST(@ModelAttribute Address1 address1) {
    try{
        log.info(format, address1.toString());
        a1Repository.save(address1);
        return "redirect:/address1/selectlist.do?id=" + address1.getMember1().getId();
    }
    catch(Exception e){
        e.printStackTrace();
        // redirect: 주소창의 주소 바꿈
        return "redirect:/home.do";
    }
}

Address1Controller.java를 생성한다.


이렇게 등록하면

오류는 뜨지만

DB에는 정상적으로 들어간다.


// address1/selectlist.do?id=b
@GetMapping(value = "/selectlist.do")
public String selectListGET( Model model,
    @RequestParam(name = "id") String id ) {
    try{
        Member1 member1 = m1Repository.findById(id).orElse(null);
        log.info(format, member1.toString()); // 오류발생시점 stackoverflow

        model.addAttribute("obj", member1);

        // redirect 없을 때는 html 표시
        return "/address1/selectlist";
    }
    catch(Exception e){
        e.printStackTrace();
        // redirect: 주소창의 주소 바꿈
        return "redirect:/home.do";
    }
}

여기서 오류가 발생한다.

@ToString.Exclude // stackoverflow
@OneToMany(mappedBy = "member1", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
@OrderBy(value = "no desc")
List<Address1> list = new ArrayList<>();

Address1.java에서 Member1의 정보를 가져오는 list에 @ToString.Exclude를 추가한다.


오류처리를 하면 이렇게 정상적으로 출력이 된다.


주소 삭제

<table border="1">
    <thead>
        <tr>
            <th>주소번호</th>
            <th>우편번호</th>
            <th>주소</th>
            <th>날짜</th>
            <th>버튼</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="tmp : ${obj.list}">
            <td th:text="${tmp.no}"></td>
            <td th:text="${tmp.postcode}"></td>
            <td th:text="${tmp.address}"></td>
            <td th:text="${tmp.regdate}"></td>
            <td>
                <form th:action="@{/address1.delete.do}" method="post">
                    <input type="hidden" name="no" th:value="${tmp.no}" />
                    <input type="submit" value="삭제" />
                </form>
            </td>
        </tr>
    </tbody>
</table>

selectlist.html을 위처럼 수정한다.

@PostMapping(value = "/delete.do")
public String deletePOST(
    @RequestParam(name="no1") long no,
    @RequestParam(name="id1") String id){
    try {
        log.info(format, no);
        a1Repository.deleteById(no);
        return "redirect:/address1/selectlist.do?id=" + id;
    }
    catch(Exception e) {
        e.printStackTrace();
        // redirect: 주소창의 주소 바꿈
        return "redirect:/home.do";
    }
}

Address1Controller.java에 위 코드를 추가한다.


53을 삭제를 누르면

이렇게 삭제된다.


// 저장소 생성 JpaRepository에는 기본적인 crud구현이 되어있음
@Repository
public interface Address1Repository extends JpaRepository<Address1, Long>{
    
    // select ... WHERE address=?
    List<Address1> findByAddress(String address);

    // select ... WHERE postcode=?
    List<Address1> findByPostcode(String postcode);

    // select ... WHERE address=? AND postcode=?
    List<Address1> findByAddressAndPostcode(String address, String postcode);

    // WHERE member1.id=? ORDER BY no DESC => member1은 객체이기 때문에 _ 를 이용해서 id값
    List<Address1> findByMember1_idOrderByNoDesc(String id);
}

Address1Repository에 위 코드를 추가한다.


// address1/selectlist.do?id=b
@GetMapping(value = "/selectlist.do")
public String selectListGET( Model model,
    @RequestParam(name = "id") String id ) {
    try{
        Member1 member1 = m1Repository.findById(id).orElse(null);
        log.info(format, member1.toString()); // 오류발생시점 stackoverflow
        model.addAttribute("obj", member1);

        List<Address1> addressList = a1Repository.findByMember1_idOrderByNoDesc(member1.getId());
        model.addAttribute("address", addressList);

        // redirect 없을 때는 html 표시
        return "/address1/selectlist";
    }
    catch(Exception e){
        e.printStackTrace();
        // redirect: 주소창의 주소 바꿈
        return "redirect:/home.do";
    }
}

Address1Controller의 selectListGET을 위처럼 수정한다


<tbody>
            <tr th:each="tmp : ${address}">

selectlist.html의 tbody에 obj.list를 address로 변경한다.


페이지네이션

// WHERE member1.id=? ORDER BY no DESC + 페이지네이션 기능 포함
List<Address1> findByMember1_idOrderByNoDesc(String id, Pageable pageable);

Address1Repository.java에 위 코드를 추가한다.


// address1/selectlist.do?id=b&page=1
@GetMapping(value = "/selectlist.do")
public String selectListGET( Model model,
    @RequestParam(name = "id") String id,
    @RequestParam(name="page", defaultValue = "0") int page ) {
    try{
        if(page==0) { // 페이지 정보가 없다면 1로 변경하기
            return "redirect:/address1/selectlist.do?id=" + id + "&page=1";
        }
        // 회원 정보
        Member1 member1 = m1Repository.findById(id).orElse(null);
        log.info(format, member1.toString()); // 오류발생시점 stackoverflow
        model.addAttribute("obj", member1);

        // 전체 개수 가져오기
        long total = a1Repository.countByMember1_id(member1.getId());
        model.addAttribute("pages", (total-1)/5+1);

        // 페이지네이션 설정
        // 1페이지는 0
        PageRequest pageRequest = PageRequest.of((page-1), 5);

        List<Address1> addressList = a1Repository.findByMember1_idOrderByNoDesc(member1.getId(), pageRequest);
        model.addAttribute("address", addressList);

        // redirect 없을 때는 html 표시
        return "/address1/selectlist";
    }
    catch(Exception e){
        e.printStackTrace();
        // redirect: 주소창의 주소 바꿈
        return "redirect:/home.do";
    }
}

Address1Controller.java를 위처럼 수정하고


</table>
<th:block th:each="num : ${numbers.sequence(1, pages)}">
    <a th:href="@{/member1/selectlist.do(text=${param.text}, page=${num})}" th:text="${num}"></a>
</th:block>
<hr />

selectlist.html에 위 코드를 추가한다.


페이지네이션이 추가된 것을 확인할 수 있다.


@Value("${address.pagetotal}") int PAGETOTAL;

...

// 전체 개수 가져오기
long total = a1Repository.countByMember1_id(member1.getId());
model.addAttribute("pages", (total-1) / PAGETOTAL + 1);

// 페이지네이션 설정, // 1페이지는 0
PageRequest pageRequest = PageRequest.of((page-1), PAGETOTAL);

Address1Controller.java를 위처럼 수정, 추가한다.

global.properties에 들어가서 원하는 총 페이지 수를 설정할 수 있다.


이미지

테이블 생성

@Data
@Entity
@Table(name = "BOARDIMAGE1")
@SequenceGenerator(name = "SEQ_BOARDIMAGE1_NO", sequenceName = "SEQ_BOARDIMAGE1_NO", initialValue = 1, allocationSize = 1)
public class BoardImage1 {
    
    // 이미지번호
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_BOARDIMAGE1_NO")
    private long no;

    // 이미지명
    private String imageName;

    // 이미지타입
    private String imageType;

    // 이미지사이즈
    private long imageSize;

    // 이미지데이터
    @Lob
    private byte[] imageData;

    // 등록일
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @CreationTimestamp
    @Column(name = "REGDATE", insertable = true, updatable = false)
    private Date regdate;
    
    // 외래키 (게시글번호)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "BRDNO", referencedColumnName = "NO")
    private Board1 board1;
}

entity 폴더에 BoardImage1.java를 생성한다.


@ToString.Exclude
@OneToMany(mappedBy = "board1", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
@OrderBy(value = "no desc")
List<BoardImage1> list1 =  new ArrayList<>();

Board1.java에도 추가한다.


spring.jpa.hibernate.ddl-auto=validate
# spring.jpa.hibernate.ddl-auto=update

application.properties에서 update를 활성화 한 후 서버를 가동하고

다시 validate를 활성화시킨다.

테이블과 시퀀스가 생성되었다.


이미지 목록

<tbody>
    <tr th:each="obj : ${list}">
        <td th:text="${obj.no}"></td>
        <td><a th:href="@{/board1/selectone.do(no=${obj.no})}" th:text="${obj.title}"></a></td>
        <td th:text="${obj.writer}"></td>
        <td th:text="${obj.hit}"></td>
        <td th:text="${obj.regdate}"></td>
        <td><a th:href="@{/boardimage1/selectlist.do(no=${obj.no})}"><button>이미지</button></a></td>
    </tr>
</tbody>

board1의 selectlist.html을 위처럼 수정한다.

이미지 버튼이 활성화되었다.


@Controller
@RequestMapping(value = "/boardimage1")
@RequiredArgsConstructor
@Slf4j
public class BoardImage1Controller {
    
    final String format="BImage => {}";

    @GetMapping(value = "/selectlist.do")
    public String selectListGET(@RequestParam(name = "no") long no ){
        try{

            return "/boardimage1/selectlist";
        }
        catch(Exception e){
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }
}

BoardImage1Controller.java를 생성한다.

이미지를 누르면 해당 게시글의 이미지로 이동한다.


이미지 등록

<hr />
    <h3>이미지등록</h3>
    <form th:action="@{/boardimage1/insertimage.do}" method="post" enctype="multipart/form-data">
        <input type="text" name="board1.no" th:value="${board1.no}" /><br />
        <input type="file" name="tmpfile" accept="image/*"/><br />
        <input type="submit" value="이미지업로드" />
    </form>
</body>

boardimage1의 selectlist.html에 위 코드를 추가한다.


@PostMapping(value = "/insertimage.do")
public String insertImagePOST(@ModelAttribute BoardImage1 image1,
    @RequestParam(name="tmpfile") MultipartFile file){
    try{
        image1.setImageSize( file.getSize() );
        image1.setImageData( file.getInputStream().readAllBytes() );
        image1.setImageType( file.getContentType() );
        image1.setImageName( file.getOriginalFilename() );
        // log.info(format, image1.toString());
        bi1Repository.save(image1);
        return "redirect:/boardimage1/selectlist.do?no=" + image1.getBoard1().getNo();
    }
    catch(Exception e){
        e.printStackTrace();
        return "redirect:/home.do";
    }
}

BoardImage1Controller.java를 위처럼 수정한다.

이렇게 업로드할 수 있다.


대표이미지

// 게시글 번호가 일치하는 것 중에서 이미지 번호가 가장 적은 것을 반환
// select* from boardimage1 where board1.no = 13 order by no asc limit 1;
BoardImage1 findTop1ByBoard1_noOrderByNoAsc(Long no);

// 게시글 번호가 일치하는 모든 이미지
// select * from boardimage1 where board1.no=8 order by no asc
List<BoardImage1> findByBoard1_noOrderbyNoAsc(Long no);

BoardImage1Repository에 추가한다.


public class BoardImage1Controller {
    
    final String format="BImage => {}";
    final Board1Repository b1Repository; // 게시글
    final BoardImage1Repository bi1Repository; // 게시글 이미지
    @Value("${default.image}") String DEFAULTIMAGE;
    final ResourceLoader resourceLoader;

    // 127.0.0.1:9090/ROOT/boardimage1/image?no=1
    @GetMapping(value = "/image")
    public ResponseEntity<byte[]> image(@RequestParam(name = "no", defaultValue = "0") long no) throws IOException{
        BoardImage1 obj = bi1Repository.findById(no).orElse(null);
        HttpHeaders headers = new HttpHeaders(); // import org.springframework.http.HttpHeaders;

        if( obj != null ){ // 이미지가 존재할 경우
            if(obj.getImageSize()> 0){
                headers.setContentType( MediaType.parseMediaType( obj.getImageType() ) );
                return new ResponseEntity<>( obj.getImageData(), headers, HttpStatus.OK);
            }
        }
        
        // 이미지가 없을 경우
        InputStream is = resourceLoader.getResource(DEFAULTIMAGE).getInputStream(); // exception 발생됨.
        headers.setContentType(MediaType.IMAGE_PNG);
        return new ResponseEntity<>( is.readAllBytes(), headers, HttpStatus.OK);
    }
    
...    

    @GetMapping(value = "/selectlist.do")
    public String selectListGET(
        Model model, HttpServletRequest request,
        @RequestParam(name = "no") long no ){
        try{
            // 게시글 정보
            Board1 board1 = b1Repository.findById(no).orElse(null);
            model.addAttribute("board1", board1);

            // 대표이미지
            BoardImage1 image1 = bi1Repository.findTop1ByBoard1_noOrderByNoAsc(no);
            board1.setImageUrl(request.getContextPath() + "/boardimage1/image?no=0");
            if(image1 != null){
                board1.setImageUrl(request.getContextPath() + "/boardimage1/image?no=" + image1.getNo());
                log.info(format, image1.toString());
            }

            // 이미지도 포함하여 view로 전달
            model.addAttribute("board1", board1);

            // 전체이미지
            List<BoardImage1> list1 = bi1Repository.findByBoard1_noOrderByNoAsc(no);
            if( !list1.isEmpty() ){ // 리스트는 비어있지 않는지 확인
                log.info(format, list1.toString());            
            }
            
            return "/boardimage1/selectlist";
        }
        catch(Exception e){
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }
}

BoardImage1Controller.java를 위처럼 수정, 추가한다.


<body>
    <h3>이미지목록</h3>
    <a th:href="@{/board1/selectlist.do}"><button>게시글목록</button></a><br />
    대표이미지<img th:src="${board1.imageUrl}" style="width:100px;height:100px" ><br />
    글번호 : <label th:text="${board1.no}"></label><br />
    글제목 : <label th:text="${board1.title}"></label>
    <hr />

    글번호에 해당하는 이미지 전체 출력

    <hr />
    <h3>이미지등록</h3>
    <form th:action="@{/boardimage1/insertimage.do}" method="post" enctype="multipart/form-data">
        <input type="text" name="board1.no" th:value="${board1.no}" /><br />
        <input type="file" name="tmpfile" accept="image/*"/><br />
        <input type="submit" value="이미지업로드" />
    </form>
</body>

selectlist.html도 위처럼 추가하면

이렇게 대표 이미지가 나오게 된다.


이미지 전체 출력

@GetMapping(value = "/selectlist.do")
public String selectListGET(
    Model model, HttpServletRequest request,
    @RequestParam(name = "no") long no){
    try {
        // 게시글정보
        Board1 board1 = b1Repository.findById(no).orElse(null);

        // 대표이미지
        BoardImage1 image1 = bi1Repository.findTop1ByBoard1_noOrderByNoAsc(no);
        board1.setImageUrl( request.getContextPath() + "/boardimage1/image?no=0" );
        if(image1 != null) {
            board1.setImageUrl( request.getContextPath() + "/boardimage1/image?no=" + image1.getNo() );
        }
        model.addAttribute("board1", board1);

        // 전체이미지
        List<String> imageList = new ArrayList<>();
        List<BoardImage1> list1 = bi1Repository.findByBoard1_noOrderByNoAsc(no);
        if( !list1.isEmpty() ) { //리스트는 비어 있지 않는지 확인
            for(BoardImage1 tmp : list1){
                imageList.add( request.getContextPath() + "/boardimage1/image?no=" + tmp.getNo() );
            }
        }

        // 전체이미지 view로 전달
        model.addAttribute("imageList", imageList);
        return "/boardimage1/selectlist";
    }
    catch(Exception e){
        e.printStackTrace();
        return "redirect:/home.do";
    }
}

BoardImage1Controller.java의 selectListGET을 위처럼 수정한다.


<body>
    <h3>이미지 목록</h3>
    <a th:href="@{/board1/selectlist.do}"><button>게시글목록</button></a><br />
    <img th:src="${board1.imageUrl}" style="width:100px;height:100px; border:1px solid #cccccc;"><br />
    글번호: <label th:text="${board1.no}"></label><br />
    글제목: <label th:text="${board1.title}"></label>
    <hr />

    <div th:each="tmp : ${imageList}" style="display: inline-block; border:1px solid #cccccc;">
        <img th:src="${tmp}" style="width:100px;height: 100px" />
    </div>
    
    <hr />

    <h3>이미지등록</h3>
    <form th:action="@{/boardimage1/insertimage.do}" method="post" enctype="multipart/form-data">
        <input type="text" name="board1.no" th:value="${board1.no}" /><br />
        <input type="file" name="tmpfile" accept="image/*" /><br />
        <input type="submit" value="이미지업로드" />
    </form>
</body>

selectlist.html을 위처럼 수정하면

위에 대표이미지, 아래에는 게시글 번호에 해당하는 이미지들을 등록 순서대로 나열하여 나오게 된다.

0개의 댓글