📑
Transaction
논리적 작업 단위(LUW, Logical Units of Work)
데이터베이스 시스템에서 복구 및 병행 시행(SELECT, INSERT, UPDATE, DELETE) 시 처리되는 작업의 논리적 단위.
데이터베이스에서 트랜잭션을 조작하는 기능은 사용자가 데이터베이스 완전성(integrity) 유지를 보장하게 한다.
*데이터베이스 시스템은 각각의 트랜잭션에 대해 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 영구성(Durability)을 보장한다. 이 성질을 각 첫글자를 따 ACID라고 한다. 이 ACID를 보장하는 것이 트랜잭션 기능.
간단한 트랜잭션은 아래 양식의 SQL 언어로 데이터베이스 내에서 실행된다.
- Begin the transaction
- Execute several queries (DB내 갱신이 아직 적용되지 않고 실행됨.)
- Commit the transaction (트랜잭션이 성공적이며, DB 내 갱신이 실제로 이루어짐.)
트랜잭션의 COMMIIT과 ROLLBACK 연산
Commit이란 하나의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관성 있는 상태에 있을 때,
하나의 트랜잭션이 끝났다는 것을 알려주기 위해 사용하는 연산이다. 이 연산을 사용하면 수행했던 트랜잭션이 로그에 저장되며, 후에 Rollback 연산을 수행했었던 트랜잭션 단위로 하는 것을 도와준다.
Rollback이란 하나의 트랜잭션 처리가 비정상적으로 종료되어 트랜잭션의 원자성이 깨진 경우, 트랜잭션을 처음부터 다시 시작하거나, 트랜잭션의 부분적으로만 연산된 결과를 다시 취소시킨다.
후에 사용자가 트랜잭션 처리된 단위로 Rollback을 진행할 수도 있다.
주의할 점
트랜잭션은 데이터를 완전성을 보장하기 위해 많은 자원들을 사용하게 된다.
자원을 사용하며 Lock을 걸게 되면 다른 사용자들은 Lock이 해제될 때까지 기다려야한다.
기다린다는 것은 성능에 좋지 않다는 의미이다.
따라서 Transaction의 범위를 최소화하여 적용하는 것이 좋다.
Transation 설정하는 법
첫번째로, 스프링에서 제공하는 트랜잭션AOP를 사용하는 방법이 있다.
트랜잭션 처리가 필요한 곳에 @Transactional 어노테이션을 붙여주면 해당 어노테이션을 인식해서 트랜잭션을 처리하는 프록시를 적용하게 된다.
@Transactional은 Proxy Mode와 AspectJ Mode가 있는데 Proxy Mode가 Default로 설정되어있다.
규칙
- public 메서드에 적용되어야한다.
- Protected, Private Method에서는 선언되어도 에러가 발생하지는 않지만, 동작하지도 않는다.
- Non-Public 메서드에 적용하고 싶으면 AspectJ Mode를 고려해야한다.
- @Transactional이 적용되지 않은 Public Method에서 @Transactional이 적용된 Public Method를 호출할 경우, 트랜잭션이 동작하지 않는다.
예시
@Transactional
public void exampleFunction() {
...
}
두번째로, 이렇게 xml에 transactional한 prefix를 줘서 설정하는 방법이 있다.
해당 prefix로 시작하는 함수명을 사용해 트랜잭션을 사용할 수 있다.
context-transaction.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" ... >
<!-- transaction 설정 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="egov.dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- <tx:method name="*" rollback-for="Exception"/> -->
<tx:method name="list*" rollback-for="Exception" />
<tx:method name="detail*" rollback-for="Exception" />
<tx:method name="reg*" rollback-for="Exception" />
<tx:method name="upd*" rollback-for="Exception" />
<tx:method name="merge*" rollback-for="Exception" />
<tx:method name="del*" rollback-for="Exception" />
<tx:method name="snd*" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="requiredTx" expression="execution(* kr.co...*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="requiredTx" />
</aop:config>
</beans>
Java의 생성자
생성자(Constructor)는 객체가 생성될때 자동으로 호출되는 특수 목적의 멤버함수(메소드)로 객체의 초기화를 위해 사용함.
- 생성자의 이름은 클래스 이름과 동일해야 한다.
- 생성자는 다른 멤버함수(메소드)와는 다르게 리턴 타입이 없다.
- 생성자는 객체가 생성될때 자동으로 한번 호출된다.
- 생성자는 매개변수 조건에 따라 여러개를 작성할 수 있다. = 오버로딩
- 생성자는 클래스에 최소 1개는 있어야 하며, 생성자 코드가 없을 경우 컴파일러가 기본생성자를 자동으로 생성한다.
*생성자 코드가 단 하나라도 작성되어 있다면, 기본생성자가 없다고 하더라도 컴파일러가 기본생성자를 자동으로 생성하지 않는다.
this.
: 객체 자신을 가리키는 참조변수로 자신의 객체에 접근할 때 사용.
주로 멤버변수와 매개변수의 이름이 동일할 떄, 이를 구분하는 용도로 사용.
this()
: 같은 클래스에서 생성자가 다른 생성자를 호출하는 경우 사용.
주로 코드의 중복을 줄이기 위해서 사용. this()는 생성자 코드에서만 사용 가능하며 해당 생성자 코드 내에서 최상단에 위치해야한다.
생성자 주입
Bean과 클래스 간 의존성 주입 시 여러 방식으로 주입이 가능한데, 생성자 주입 방식을 사용하는 것이 권장된다.
생성자 주입 방식이 권장되는 이유
- 필드 주입의 단점
필드 주입을 사용하면 편리하고 코드가 간결하긴하지만 외부에서 수정이 불가하고 반드시 Spring같은 DI를 지원하는 프레임워크가 있어야 사용할 수 있다는 단점이 있다.
메인코드가 필드 주입으로 작성되며 메인코드는 DI 프레임워크 위에서 동작하지만 테스트 코드는 그러지 않기 때문에 (의존성주입이 제대로 되지 않음) NullPointError가 발생한다.
또한, @Autowired 어노테이션으로 의존성 주입을 남발할 수 있다는 문제가 있다. 10개 이상의 의존성이 @Autowired 어노테이션으로 주입된 클래스를 생성자 주입 방식으로 리팩토링 한다고하면, 생성자의 매개변수가 엄청나게 많아지는데, 해당 객체의 생성자 매개변수가 많아진다는 것은 의존성, 결합에 대한 문제가 발생할 가능성도 커지고 해당 객체의 역할이 많아지면서 단일 책임 원칙에 위배될 수 있다는 것이다.
- 수정자 주입의 단점
setter의 경우 public으로 구현하기 때문에 관계를 주입받는 객체의 변경 가능성을 열어두는데, 변경 가능성을 열어두면 다른 곳에서 임의로 객체를 변경할 수 있기 때문에 에러 발생 위험이 높아진다.
- 객체의 불변성 확보
객체의 생성자는 객체 생성 시 1회만 호출되므로 주입받은 객체가 불변 객체여야하거나 반드시 해당 객체의 주입이 필요한 경우 사용한다. 생성자로 한 번 의존 관계를 주입하면, 생성자는 다시 호출될 일이 없기 때문에 불변 객체를 보장한다.
- 순환 참조 방지
순환 참조에 의한 순환 호출 에러가 발생했을 때, 필드 주입이나 수정자 주입 방식은 에러가 어플리케이션이 실행 중에 발생하므로 개발자가 어느 부분이 잘못 되었는지 찾기가 어렵다.
그러나 생성자 주입 방식을 사용하면 어플리케이션이 실행되는 시점(서비스되기 전)에 에러를 알려주므로 개발자가 순환참조 문제를 알아채고 해결할 수 있게 된다.
- 개발자의 의존성주입 실수 방지(final 키워드)
개발자가 의존성 주입에 있어 실수로 하나를 빼먹거나 할 수 있는데 이 때, 생성자 주입 방식을 취하면 final 키워드를 쓸 수 있기 때문에 컴파일 단계에서 에러가 발생한다. 나머지 주입 방식은 모두 애플리케이션 실행 도중(비즈니스 로직 실행 시)에 에러가 발생하여 평소에 오류를 확인할 수 없으므로 문제가 된다.
*생성자 주입을 제외한 나머지 주입 방식은 모두 생성자 이후에 호출되어 필드에 final 키워드를 사용할 수 없다.
Json 역직렬화
컨트롤러를 수정하다가 이런 오류를 마주쳤다..
JSON parse error: Cannot deserialize instance
of `java.util.ArrayList<java.lang.Object>` out of VALUE_STRING token;
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot deserialize instance of `java.util.ArrayList<java.lang.Object>`
out of VALUE_STRING token
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
public void setRegNoList(List<String> RegNoList) {
RegNoList = RegNoList;
}
해당 리스트 변수가 포함된 클래스(내 경우는 DTO)에 @JsonFormat(with =JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)을 추가해주어서 간단하게 해결은 했지만 생기는 의문..
비슷한 기능이 두개의 url로 나뉘어져 매핑되어있어서 하나로 합치려했을 뿐인데
로직을 바꾸지도 않았는데 잘 돌아가던 코드에서 왜 이런 오류가 생긴걸까?
...
이유를 알게됐다..
render: function(data, type, row)
{
var html = '<button type="button" class="btn btn-sm btn-primary" onclick="fnRestoreCtmmny_click(\''
+ row.ctmmnyRegNo + '\');">고객복구</button>';
return html;
}
지금 이렇게 ctmmnyRegNo 데이터 하나를 받아다가 사용을 하고 있는데 보이는 바와 같이 List타입으로 사용하고 있지 않기 때문에 오류가 나는 것이었다.
따라서 함수를 이렇게 변경해주면 짜잔.. @JsonFormat(with=JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)를 쓰지 않아도 됩니다~
function fnRestoreCtmmny(ctmmnyRegNo) {
var arr = [ctmmnyRegNo];
alertConfirmModal('알림', '고객정보를 복구하시겠습니까?', function () {
$.ajax({
url: '${pageContext.request.contextPath}/bsnctmmny/restore.do',
type: 'POST',
contentType: 'application/json',
data:JSON.stringify({
ctmmnyRegNoList: arr,
}),
success : function(data, status, xhr) {
if (data.resultCd == 'S') {
alertSuccessModal('성공', '복구되었습니다.');
} else {
alertDangerModal('오류', '처리중 오류가 발생했습니다. <br>' + data.resultMsg);
}
$('#layerModalArea').modal('hide');
fnBsnCtmmnySearch();
},
error: function (error, status, xhr) {
console.log(error);
}
});
})
}