스프링
트랜잭션 처리
두 개 이상의 쿼리를 한 작업으로 실행해야 할 때 사용하는 것이 트랜잭션이다.
트랜잭션은 여러 쿼리를 논리적으로 하나의 작업으로 묶어준다. 묶인 쿼리 중 하나라도 실패하면 전체 쿼리를 실패로
간주하고 실패 이전에 실행한쿼리를 취소한다. ( 롤백 ) 묶인 쿼리가 모두 성공 후 실제 반영하면 ( 커밋 )
@Transactional을 이용한 트랜잭션 처리
해당 애노테이션을 사용하면 트랜잭션 범위를 쉽게 지정할 수 있다.
예시 ) ChangPasswordService 메서드에 @Transactional애노테이션 설정
public class ChangePasswordService {
@Autowired
private MemberDao memberDao;
@Transactional
public void changePassword(String email, String oldPwd, String newPwd) {
Member member = memberDao.selectByEmail(email);
if (member == null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
}
위에 설정으로 dao.selectByEmail()에서 실행하는 쿼리와 dto.changePassword()에서
실행하는 쿼리는 한 트랜잭션에 묶임 ( 밑에 코드 )
public class MemberDao {
private JdbcTemplate jdbcTemplate;
public MemberDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Member selectByEmail(String email) {
String sql = "select * from MEMBER where EMAIL = ?";
List<Member> result = jdbcTemplate.query(sql, new MemberRowMapper(), email);
return result.isEmpty() ? null : result.get(0);
}
public void changePassword(String oldPassword, String newPassword) {
if (!password.equals(oldPassword))
throw new WrongIdPasswordException();
this.password = newPassword;
}
그리고 나서 플랫폼 트랜잭션 매니저 빈 설정
@Configuration
public class AppCtx {
@Bean(destroyMethod = "close") // 빈이 소멸될 때 커넥션 풀을 닫도록 설정
public DataSource dataSource() {
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring5fs?"
+"useSSL=true&useUnicode=true&characterEncoding=utf8");
ds.setUsername("spring5");
ds.setPassword("spring5");
ds.setInitialSize(2); // 초기 커넥션 개수 지정
ds.setMaxActive(10); // 최대 커넥션 개수 지정
return ds;
}
@Bean
public MemberDao memberDao() {
return new MemberDao(dataSource());
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource());
return tm;
}
실행 테스트
package main;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import config.AppCtx;
import spring.ChangePasswordService;
import spring.MemberNotFoundException;
import spring.WrongIdPasswordException;
public class MainForCPS {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
ChangePasswordService cps =
ctx.getBean("changePasswordService", ChangePasswordService.class);
try {
cps.changePassword("qqqq@wwe.com", "123", "111");
System.out.println("암호를 변경했습니다.");
}catch (MemberNotFoundException e) {
System.out.println("회원 데이터가 존재하지 않습니다.");
}catch (WrongIdPasswordException e) {
System.out.println("암호가 올바르지 않습니다.");
}
ctx.close();
}
}
오라클
PRIMARY KEY, FOREIGN KEY
참조 대상 테이블을 부모, 참조하는 테이블을 자식으로 표현함
참조 대상이 될 DEPT_FK 테이블 생성
create table dept_fk (
deptno NUMBER(2) CONSTRAINT deptfk_deptno_pk PRIMARY key,
dname VARCHAR2(14),
loc VARCHAR2(13)
)
DEPT_FK 테이블의 DEPTNO 열을 참조하는 FOREIGN KEY 제약 조건을 정의한 EMP_FK 테이블 생성
CREATE table emp_fk(
empno NUMBER(4) CONSTRAINT empfk_empno_pk PRIMARY key,
ename VARCHAR2(10),
job VARCHAR2(9),
mgr NUMBER(4),
hiredate date,
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(2) CONSTRAINT empfk_deptno_fk REFERENCES dept_fk(deptno)on delete set null
)
EMP_FK 테이블에 데이터 삽입 ( DEPENO 데이터가 아직 DEPT_FK 테이블에 없을 때 에러남 )
insert into emp_fk (empno, ename, job, mgr, hiredate, sal, comm, deptno)
values (9999, 'TEST_NNAME', 'TEST_JOB', null, '2001/01/01',3000,null,10)
DEPT_FK에 데이터 삽입 후 EMP_FK에 데이터 삽입해야 가능
insert into dept_fk (deptno, dname, loc)
VALUES(10, 'TEST_DNAME', 'TEST_LOC')
insert into emp_fk (empno, ename, job, mgr, hiredate, sal, comm, deptno)
values (9999, 'TEST_NNAME', 'TEST_JOB', null, '2001/01/01',3000,null,10)
현재 이 상태는 DEPT_FK 테이블에 10번 부서 데이터가 저장되어 있고 EMP_FK 테이블에는 이 10번 부서를 참조하는
데이터가 있다. 이 경우에는 DEPT_FK 테이블의 DEPTNO 열에 저장된 10번 부서 데이터는 삭제할 수 없다.
이유는 삭제하려는 DEPTNO 값을 참조하는 데이터가 존재하기 때문이다. 삭제하려면 방법이 2가지 정도 있다.
1. 참조하고 있는데 EMP_FK의 DEPENO가 10번인 데이터를 삭제한 후 DEPT_FK 테이블의 10번 부서 삭제
delete from emp_fk
where deptno = 10
delete from dept_fk
where deptno = 10
2. EMP_FK 테이블의 DEPTNO가 10번인 데이터를 다른 부서 번호나 NULL로 변경한 후 DEPT_FK 10번부서 삭제
delete from dept_fk
where deptno = 10
보통 2번 방법으로 많이 사용
기본 값을 정하는 DEFAULT
테이블을 생성할 때 DEFAULT 제약 조건 설정하기
create table table_default (
login_id VARCHAR2(20) CONSTRAINT tblck2_loginid_pk PRIMARY key,
login_pwd VARCHAR2(20) DEFAULT '1234',
tel VARCHAR2(20)
)
JSP
세션 ( session )
세션은 클라이언트의 상태 값을 저장할 수 있고 서버에 값이 저장된다.
세션을 사용하면 서버는 클라이언트의 상태 값을 유지할 수 있기 때문에, 인증된 사용자 정보를 유지하기 위한 목적으로
세션을 많이 사용 한다. ( 로그인 )
세션은 웹 브라우저마다 따로 존재하기 때문에 웹 브라우저와 관련된 1대 1정보를 저장하기 알맞은 장소이다.
세션 생성하기
세션 기본 객체
세션의 기본객체는 request 기본객체, application 기본객체 ( 2개 많이 사용함 ) pageContext 기본객체와
마찬가지로 속성을 제공하며,
setAttribute(), getAttribute()등의 메서드를 사용하여 속성값을 저장하거나 읽어올 수 있다.
세션 ID는 쿠키를 통해서 웹 브라우저에 전달된다.
세션 기본 객체의 속성 사용
한번 생성된 세션은 지정된 유효 시간 동안 유지된다. 세션 기본객체는 웹 브라우저의 여러 요청을 처리하는 JSP 페이지
사이에서 공유된다.
예제)
setMember.jsp ( 세션에 저장 )
속성에 값을 저장할 때에는 request 기본객체와 마찬가지로 setAtrribute() 메서드를 사용하며, 속성 값을 사용할 때에는
getAttribute() 메서드를 사용
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
session.setAttribute("MEMBERID", "test");
session.setAttribute("NAME", "홍길동");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
세션에 저장함
</body>
</html>
getMember.jsp ( 속성 값 사용 )
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<%=session.getAttribute("MEMBERID") %><br/>
<%=session.getAttribute("NAME") %>
</body>
</html>
세션 종료
세션 유지할 필요가 없는 경우 session.invalidate() 메서드를 사용하면 세션 종료됨 ( 로그아웃 )
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
session.invalidate();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
세션이 종료됨
</body>
</html>
세션이 종료되면 기존에 사용하던 세션 기본 객체가 삭제되고, 다음에 세션을 사용할 때에는 새로운 세션 기본객체가 사용
세션의 유효 시간
세션은 최근 접근 시간이라는 개념을 갖고 있다. 세션 기본 객체가 사용될 때마다 세션은 최근 접근 시간은 갱싱된다.
유효 시간이 없는 상태에서 session.invalidate()를 명시적으로 실행하지 않는다면, 한번 생성된 세션 객체는 계속 메모리에 남아 있게 되고, 시간이 흐르게 되면 제거되지 않은 세션 객체 때문에 메모리 부족 현상을 발생하게 된다.
방법 1 : web.xml 파일에 유효 시간을 지정하는 방법 / 값의 단위는 분 아래 코드는 50분
방법 2 : 세션 기본 객체가 제공하는 setMaxInactiveInterval()메서드를 사용하는 방법 / 값의 단위는 초 단위
밑에 코드는 60분
<%
session.setMaxInactiveInterval(60*60);
%>
request.getSession()을 이용한 세션 생성
세션이 생성된 경우에만 세션 객체를 사용하고 싶은 경우에는 getSession() 메서드에 false를 파라미터로 전달해주면 된다. request.getSession(false) 를 실행하면 세션 객체가 생성된 경우에는 세션 객체를 리턴하고 세션 객체가 생성되어 있지 않은 경우에는 null을 리턴한다.
세션을 사용한 인증 정보 유지
1. 로그인에 성공하면 session 기본 객체의 특정 속성에 데이터를 기록한다.
2. 이후로 session 기본 객체의 특정 속성이 존재하면 로그인한 것으로 간주한다.
3. 로그아웃 할 경우 session.invalidate() 메서드를 호출하여 세션을 종료 한다.
예제 )
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String id = request.getParameter("id");
String password = request.getParameter("password");
if(id.equals(password)){
session.setAttribute("MEMBERID", id);
%>
<!DOCTYPE html>
<html>
<title>로그인 성공</title>
<head>
<meta charset="UTF-8">
</head>
<body>
로그인에 성공했습니다.
</body>
</html>
<%
}else{
%>
<script>
alert("로그인에 실패하였습니다.");
history.go(-1);
</script>
<%
}
%>
loginForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<title>로그인폼</title>
<head>
<meta charset="UTF-8">
</head>
<body>
<form action="<%= request.getContextPath() %>/member/sessionLogin.jsp" method="post">
아이디 <input type="text" name="id" size="10">
암호 <input type="password" name="password" size= "10">
<input type="submit" value="로그인">
</form>
</body>
</html>
loginCheck.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String memberId = (String)session.getAttribute("MEMBERID");
boolean login = memberId == null ? false : true;
%>
<!DOCTYPE html>
<html>
<title>로그인 체크</title>
<head>
<meta charset="UTF-8">
</head>
<body>
<%
if(login){
%>
아이디 "<%= memberId %>"로 로그인 한 상태
<%
}else{
%>
로그인하지 않은 상태
<%
}
%>
</body>
</html>
logOut.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
session.invalidate();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그아웃</title>
</head>
<body>
로그아웃
</body>
</html>
loginform.jsp에서 입력 한 아이디, 비밀번호가 같아야 login.jsp로 이동되서 세션에 저장
세션 유지한 상태에서 loginCheck.jsp 실행시 로그인 유지상태 확인
logOut 실행 시 세션 종료
필터 ( Filter )
클라이언트로부터 오는 요청(request)과 최종 자원 ( 서블릿, jsp, 기타 문서 ) 사이에 위치 클라이언트로부터 오는 요청정보를 알맞게 변경 가능 ( 웹페이지 서블릿 가운데에 위치 ) 서블릿 실행 전 필터 실행
클라이언트와 자원 사이에 한 개 의 필터만 존재 할 수 있는 것은 아니며, 여러 개의 필터가 모여 하나의 필터 체인을 형성하게 된다. 필터는 사용자 인증이나 권한 검사와 같은 기능을 구현할 때 사용할 수 있다.
다이나믹 웹프로젝트에서 필터 생성 가능 ( 서블릿이랑 비슷한 옵션 )
package filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class LoginCheckFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession session = httpRequest.getSession(false);
boolean login = false;
if (session != null) {
if (session.getAttribute("MEMBER") != null) {
login = true;
}
}
if (login) {
chain.doFilter(request, response); // 다음 필터 처리
} else {
RequestDispatcher dispatcher = request
.getRequestDispatcher("/loginForm.jsp");
dispatcher.forward(request, response);
}
}
@Override
public void destroy() {
}
}
위에 코드에 메서드 기능은
init() : 필터를 초기화할 때 호출된다.
doFilter() : 체인을 따라 다음에 존재하는 필터로 이동한다. 체인의 가장 마지막에는 클라이언트가 요청한 최종 자원이
위치한다.
destroy() : 필터가 웹 컨테이너에서 삭제될 때 호출된다.
필터 설정하기 : web.xml 이용
필터를 설정하는 방법은 두 가지가 있는데, 첫 번째 방법은 web.xml 파일에 관련 정보를 추가하는 것이며,
두 번째 방법은 @WebFilter 애노테이션을 사용하는 것이다.
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>LoginCheck</filter-name>
<filter-class>filter.LoginCheckFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginCheck</filter-name>
<url-pattern>/board/*</url-pattern>
</filter-mapping>
</web-app>
<filter> 태그는 사용될 필터를 지정하는 역할을 하며,
<filter-mapping> 태그는 특정 자원에 대해 어떤 필터를 사용할지를 지정한다. 위에 예제는 /board/ 로 시작하는 모든 확장자를 갖는 자원을 요청할 경우 FilterName 필터가 사용되도록 지정하고 있다.
<init-param> 태그는 필터의 init()메서드가 호출될 때 전달되는 파라미터 값이다. 이는 서블릿의 초기화 파라미터와 비슷한 역할을 하며 주로 필터를 사용하기 전에 초기화해야 하는 객체나 자원을 할당할 때 필요한 정보를 제공하기 위해 사용된다.
<url-pattern> 태그는 클라이언가 요청한 특정 URI에 대해서 필터링 할 때 사용된다.
예제)
로그인 검사 필터
해당 필터는 로그인을 한 상태라면 필터 체인의 다음 필터로 이동하고 로그인을 하지 않은 상태로 판단되면 로그인 페이지로 이동하게 된다.
package filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class LoginCheckFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession session = httpRequest.getSession(false);
boolean login = false;
if (session != null) {
if (session.getAttribute("MEMBER") != null) {
login = true;
}
}
if (login) {
chain.doFilter(request, response);
} else {
RequestDispatcher dispatcher = request
.getRequestDispatcher("/loginForm.jsp");
dispatcher.forward(request, response);
}
}
@Override
public void destroy() {
}
}
web.xml
/board/*에 해당하는 요청을 보내면 LoginCheckFilter가 동작하게 되고, 세션에 "MEMBER"속성이 존재하지 않으면 /loginForm.jsp로 포워딩하게된다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>LoginCheck</filter-name>
<filter-class>filter.LoginCheckFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginCheck</filter-name>
<url-pattern>/board/*</url-pattern>
</filter-mapping>
</web-app>
Member 속성이 존재하지 않은 상태에서 boardList.jsp 요청
loginForm.jsp로 이동 board/ 뒤에는 아무거나 써도 됨
로그인 처리 ( "Member" 속성 넣은 후 boardList.jsp 실행 )
'프로젝트 기반 자바(JAVA) 응용 SW개발자 취업과정' 카테고리의 다른 글
2023-08-09 59일차 (0) | 2023.08.09 |
---|---|
2023-08-08 58일차 (0) | 2023.08.08 |
2023-08-04 56일차 (0) | 2023.08.04 |
2023-08-03 55일차 (0) | 2023.08.03 |
2023-08-02 54일차 (0) | 2023.08.02 |