# 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로 변경한다.
변경하여도 정상적으로 잘 작동한다.
@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을 위처럼 수정하면
위에 대표이미지, 아래에는 게시글 번호에 해당하는 이미지들을 등록 순서대로 나열하여 나오게 된다.