#230926 수업 복습
외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴
객체간 의존성을 개발자가 객체 내부에서 직접 호출하는 대신,
외부(스프링 컨테이너)에서 객체를 생성해서 넣어주는 방식
Hotel, Restaurant 객체 안에 Chef 객체를 주입
package com.kh.demo.sample;
import org.springframework.stereotype.Component;
@Component //@Component : 어떤 스프링의 요소라는 것을 어노테이션
public class Chef {
}
package com.kh.demo.sample;
import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
@ToString
@Getter
//@AllArgsConstructor //인스턴스 변수로 선언된 모든 것을 파라미터로 받는 생성자를 작성
//@RequiredArgsConstructor : 특정 변수를 위한 생성자를 만들 때 작성한다.
// final이나 @NonNull이 붙은 인스턴스 변수에 대한 생성자를 만들어낸다.
@RequiredArgsConstructor
@Component
public class Hotel {
@NonNull //chef만 주입할 때
private Chef chef;
int data;
}
package com.kh.demo.sample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
//@Data : @Getter, @Setter, @ToString, ... 을 하나로 합친 어노테이션
@Getter
@Component
public class Restaurant {
//주입 방법 : 1. 생성자로 주입(@Data) 2. setter로 주입
//setter가 호출될 때 @Autowired(주입)
@Setter(onMethod_ = @Autowired) //롬복 사용시에는 언더바 붙여야함
//@Autowired만 써도 됨
private Chef chef;
}
Restaurant 객체는 Chef 객체가 필요하다는 어노테이션(@Autowired) 설정이 있으므로,
등록되어 있던 Chef 객체의 레퍼런스를 Restaurant 객체에 주입한다.
=> Restaurant 객체가 Chef 객체를 사용하고 있는 경우,
Restaurant는 Chef에 의존성이 있다고 할 수 있음
=> Chef의 무언가가 변하면 그것이 Restaurant에 전달되어 영향을 미침.
@Autowired
생성자에 @Autowired를 붙여 의존성을 주입 받을 수 있음
필드에 바로 의존 관계를 주입하는 방법
외부에서 접근이 불가능하다는 단점이 있어 거의 사용되지 않음
setter의 경우 객체가 변경될 필요성이 있을 때만 사용
자바 프로그래밍 언어용 유닛 테스트 프레임워크
가장 많이 사용되는 테스트 환경
테스트 성공시 JUnit GUI 창에 녹색 / 적색으로 표시
하나하나의 케이스별로(단위로 나누어서) 테스트를 하는 단위 테스트 도구
1. 테스트 클래스 위쪽에 어노테이션 추가
@SpringBootTest
2. 내부에 테스트용 메소드 선언
내용은 우리가 테스트 해볼 로직으로 구현
위쪽에 @Test 작성
3. 해당 클래스 JUnit Test로 실행
package com.kh.demo.sample;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class RestaurantTests {
@Autowired
private Restaurant restaurant;
//@Test : JUnit에서 테스트 대상임을 표시
@Test
@DisplayName("테스트 이름")
public void testExist() {
//assert~~ : ~~이면 테스트 통과
// assertNull(restaurant.getChef()); getChef()가 null이면 통과->null이 아니면 테스트 실패
assertNotNull(restaurant.getChef()); //getChef()가 not null이면 통과
System.out.println(restaurant);
}
}
SQL이 짧고 간결한 경우에는 어노테이션을 이용해서 쿼리문을 작성해준다.
SQL이 복잡하거나 길어지는 경우에는 어노테이션보다 XML을 이용하는 것이 좋다.
Mapper 인터페이스 위에는 @Mapper 를 작성해준다.
Spring-MyBatis의 경우 Mapper 인터페이스와 XML을 연동해서 동시에
이용할 수 있다.
mapper인터페이스객체.메소드() 를 호출하는 순간
해당하는 인터페이스의 경로를 namespace로 가지고 있는 mapper XML 파일로 찾아가
메소드명과 동일한 id를 가지고 있는 쿼리문을 수행하고 결과로 돌려준다.
src/main/java에 mapper패키지 생성 > Mapper 인터페이스 생성
package com.kh.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface TimeMapper {
//Mapper 인터페이스
//Mapper를 작성하는 작업은 XML을 이용할 수도 있지만,
//최소한의 코드로 작성하기 위해서는 Mapper 인터페이스를 사용한다.
//1. xml파일 없이 바로 연동
@Select("select now() from dual")
public String getTime1();
//2. xml 파일을 이용하여 연동
public String getTime2();
}
mapper 인터페이스를 만들고, @Mapper 어노테이션을 하면 바로 mapper로 만들어짐
=> 쿼리문 작성하는 xml 파일이 없어도 쿼리문을 돌릴 수 있음
ex) @Select("select 쿼리문")
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace는 mapper 인터페이스의 경로를 그대로 써준다. -->
<mapper namespace="com.kh.demo.mapper.TimeMapper">
<!-- 'id 속성 값'은 쿼리를 사용할 때 호출할 '메소드의 이름'과 동일하게 맞추어야 한다. -->
<select id="getTime2">
select now() from dual
</select>
</mapper>
MyBatis 관련 테스트는 스프링 전체 구동이 필요하지 않기 때문에
@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
두 가지의 어노테이션을 작성해준다.
package com.kh.demo.persistence;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import com.kh.demo.mapper.TimeMapper;
@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MapperTests {
@Autowired
private TimeMapper mapper;
@Test
public void getTime1Test() {
System.out.println(mapper.getTime1());
}
@Test
public void getTime2Test() {
System.out.println(mapper.getTime2());
}
}
데이터 로직을 작성하는 곳
* 관리자 로그인, 회원 로그인, 회원가입 기능 세 가지가 있다고 가정
package com.kh.demo.repository;
public interface Repository {
Object getObj(String id);
}
=> 이후에 UserRepository, AdminRepository에서
Repository 인터페이스 상속 받아서 구현
package com.kh.demo.repository;
public interface UserRepository extends Repository {
int save(Object obj);
}
package com.kh.demo.repository;
public interface AdminRepository extends Repository{
}
=> Repository 상속 받아서 필요한 세부 repository 인터페이스를 생성하여 설계만 해 놓음(설계자 역할)
=> Impl 클래스에서 기능 구현(개발자 역할)
Impl 클래스에는 @Repository 어노테이션 작성
package com.kh.demo.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.kh.demo.mapper.AdminMapper;
@Repository
public class AdminRepositoryImpl implements AdminRepository{
@Autowired
private AdminMapper mapper;
@Override
public Object getObj(String id) {
return mapper.get(id);
}
}
package com.kh.demo.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.kh.demo.domain.UserDTO;
import com.kh.demo.mapper.UserMapper;
@Repository
public class UserRepositoryImpl implements UserRepository{
@Autowired
private UserMapper mapper;
@Override
public Object getObj(String id) {
return mapper.get(id);
}
@Override
public int save(Object obj) {
return mapper.insert((UserDTO)obj);
}
}
package com.kh.demo.domain;
import lombok.Data;
@Data
public class AdminDTO {
private String id;
private String name;
private int age;
}
package com.kh.demo.domain;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class UserDTO {
private String id;
private String name;
private int age;
private String regdate;
}
package com.kh.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.kh.demo.domain.AdminDTO;
@Mapper
public interface AdminMapper {
AdminDTO get(String id);
}
package com.kh.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.kh.demo.domain.UserDTO;
@Mapper
public interface UserMapper {
int insert(UserDTO user);
UserDTO get(String id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace는 Mapper 인터페이스의 경로를 그대로 써준다. -->
<mapper namespace="com.kh.demo.mapper.AdminMapper">
<select id="get" resultType="com.kh.demo.domain.AdminDTO">
select * from admin where id=#{id}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace는 Mapper 인터페이스의 경로를 그대로 써준다. -->
<mapper namespace="com.kh.demo.mapper.UserMapper">
<!-- id 속성 값은 쿼리를 사용할 때 호출할 메소드의 이름과 동일하게 맞추어야 한다. -->
<insert id="insert">
insert into user values(#{id},#{name},#{age},now())
</insert>
<select id="get" >
select * from user where id=#{id}
</select>
</mapper>
package com.kh.demo.repository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import com.kh.demo.domain.UserDTO;
@SpringBootTest
public class RepositoryTests {
@Autowired @Qualifier("adminRepositoryImpl")
//@Autowired 뒤에 @Qualifier("주입할 클래스명")를 붙이면 여러 개의 동일한 타입의 빈이 존재할 때 어떤 빈을 주입해야하는지 지정할 수 있음
private AdminRepository arep;
@Autowired
private UserRepository urep;
// @Test
// public void adminGet() {
// System.out.println(arep.getObj("admin"));
// }
@Test
public void userInsert() {
UserDTO obj = UserDTO.builder()
.id("apple")
.name("김사과")
.age(10)
.build();
System.out.println(urep.save(obj));
}
@Test
public void userGet() {
System.out.println(urep.getObj("apple"));
}
}
Service는 비즈니스 로직 수행
데이터베이스에 접근하는 DAO를 이용해서 결과값을 받아옴
package com.kh.demo.service;
public interface Service {
Object getDetail(String id);
}
package com.kh.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import com.kh.demo.repository.AdminRepository;
public class AdminServiceImpl implements Service {
@Autowired
private AdminRepository repository;
@Override
public Object getDetail(String id) {
return repository.getObj(id);
}
}