Spring Boot 게시판 만들기 2 : DB 연동 및 Mybatis 설정

Spring Boot - ver 2.1.8

Gradle

Java - ver 1.8

 

application.properties

내에 datasource 관련 설정값 선언

1
2
3
4
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://ip:3306/~?useSSL=false&serverTimezone=UTC
spring.datasource.username=id
spring.datasource.password=pw
cs

 

스프링부트 앱을 구동시키는 main 메소드 내의 @SpringBootApplication 을 들어가보면 @EnableAutoConfiguration 어노테이션이 존재.

해당 어노테이션이  프로젝트 내 Bean 들의 DI 주입을 자동으로 설정해준단다.

(Spring Framework 프로젝트의 web.xml 파일 내에 context.xml 를 매핑해주는 과정을 직접 하지 않아도 된다고 한다)

이 이상 자세히 파기엔 내공의 한계가 있어 일단 나중에 더 공부해보자..

 

위와 같이 프로퍼티에 datasource 관련 설정값만 선언해주면

아래와 같은 코드와 같다고 보면 된다.

1
2
3
4
5
6
7
8
9
10
    @Bean
    public DataSource customDataSource() {
 
        return DataSourceBuilder.create()
                                .url("jdbc:mysql://ip:3306/!?useSSL=false&serverTimezone=UTC")
                                .driverClassName("com.mysql.cj.jdbc.Driver")
                                .username("id")
                                .password("pw")
                                .build();
    }
cs

 

다음으로,

SqlSessionFactory 와 SqlSessionTemplate 설정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.jpp.main.config;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@Configuration
@MapperScan(value= {"com.jpp.main.mapper"})
@EnableTransactionManagement
public class MybatisConfig {
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
            SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(dataSource);
            
            Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml");
            
            sessionFactory.setMapperLocations(res);
            
            return sessionFactory.getObject();
    }
 
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
 
cs

위와 같이 datasource 관련 설정을 application.properties 에 설정하고,

mybatis 관련 설정은 java config 로 설정할 수 있다. (mybatis 는 application.properties 에 설정 불가)

application.properties 에 설정 가능한 값들은 여기를 참고

 

 datasource도 application.properties 대신 java config 로 설정하고 싶다면 아래와 같이 datasource 를 @Bean 으로 설정해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.jpp.main.config;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@Configuration
@MapperScan(value= {"com.jpp.main.mapper"})
@EnableTransactionManagement
public class MybatisConfig {
    
    @Bean
    public DataSource customDataSource() {
        return DataSourceBuilder.create()
                                .url("jdbc:mysql://ip:3306/~?useSSL=false&serverTimezone=UTC")
                                .driverClassName("com.mysql.cj.jdbc.Driver")
                                .username("id")
                                .password("pw")
                                .build();
    }
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
            SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(dataSource);
            
            Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml");
            
            sessionFactory.setMapperLocations(res);
            
            return sessionFactory.getObject();
    }
 
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
 
cs

17 Line : @MapperScan : com.jpp.main.mapper 경로 밑에 있는 mapper Interface 를 스캔하여 빈으로 등록 (mapper Interface 대신 DAO를 사용할 경우 필요 없음)

36 Line : getResources("classpath:mapper/*Mapper.xml");

~Mapper.xml 파일명 기준 모든 sql mapper 를 sqlSessionFactory에 등록

 

 

[mainMapper.xml]

간단한 select 쿼리를 작성하여 Mapper를 작성해준다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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" >
 
<mapper namespace="com.jpp.main.mapper.MainMapper">
 
    <select id="getUserList" resultType="com.jpp.main.vo.UserVO">
        select empno as no, emp_nm as name 
        from emp_info
        limit 10
    </select>
 
</mapper>
 
 
cs

 

* Mapper Interface 사용 vs DAO 사용

1) DAO 사용시

1-1) MainDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.jpp.main.dao;
 
import java.util.List;
 
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
import com.jpp.main.vo.UserVO;
 
@Repository
public class MainDAO {
    
   @Resource
    private SqlSessionTemplate sqlSessionTemplate;
    
    private static final String MAPPER_NM = "com.jpp.main.mapper.MainMapper.";
    
    public List<UserVO> getUserList(){
        return sqlSessionTemplate.selectList(MAPPER_NM+"getUserList");
    }
}
 
cs

1-2) MainService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.jpp.main.service;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.jpp.main.dao.MainDAO;
import com.jpp.main.mapper.MainMapper;
import com.jpp.main.vo.UserVO;
 
@Service
public class MainService {
    @Autowired
    private MainDAO mainDao;
    
    public List<UserVO> getUserList() throws Exception {
        return mainDao.getUserList();
    }
}
 
cs

 

 

2) mapper Interface 사용시

2-1) MainMapper

1
2
3
4
5
6
7
8
9
10
11
package com.jpp.main.mapper;
 
import java.util.List;
 
import com.jpp.main.vo.UserVO;
 
public interface MainMapper {
    
    public List<UserVO> getUserList() throws Exception;
}
 
cs

 

2-2) MainService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.jpp.main.service;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.jpp.main.dao.MainDAO;
import com.jpp.main.mapper.MainMapper;
import com.jpp.main.vo.UserVO;
 
@Service
public class MainService {
 
    @Autowired
    private MainMapper mainMapper;
    
    public List<UserVO> getUserList() throws Exception {
        
        return mainMapper.getUserList();
    }
}
 
cs

 

 

 

반응형

1. 우클릭 > New > Spring Starter Project

 

2. 프로젝트명 입력

   Gradle / Maven 선택,

   배포 파일형태 Jar/War 선택

   Java 버전 선택

 

3. Boot Version 선택

   사용할 Library 선택(WEB, JPA 등)

 

4. @SpringBootApplication 어노테이션이 붙은, (main 메소드를 지닌) 클래스의 이름을

Application 으로 수정 밑 실행(Run As > Spring Boot App)

 

5. 프로젝트 우클릭 > Gradle > Refresh Gradle Project

 

 

※ Lombok 설치 (참고)

1) lombok-1.18.10.jar 가 위치한 경로로 이동 (버전은 다를 수 있음)

2) jar 가 위치한 경로에서 shift + 우클릭하여 Powershell 실행

3) java -jar lombok-1.18.10.jar 로 lombok jar 실행 

4) Specify location 버튼을 누른 후 sts.exe(혹은 사용중인 IDE 실행파일) 선택

 

5) 완료 화면

 

6) IDE 재시작

7) sts.ini 파일을 연 후

-vmargs

-javaagent:lombok.jar (약간 다를 수 있음)

위의 내용이 추가 되었는지 확인(sts.exe와 동일한 경로에 위치)

 

참고 :

https://jojoldu.tistory.com/250?category=635883

 

 

반응형

배열안에 담겨있는 값(VO)들을 특정 기준으로 비교하여 순서를 정렬하기 위한 방법을 알아보자.

 

1. Car 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package comparator;
 
public class Car {
    
    private String name;
    private int price;
    
    public Car (String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
}
cs

 

2. Car 객체 정렬 및 실행을 위한 Main 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package comparator;
 
import java.util.Arrays;
 
public class Main {
 
    public static void main(String[] args) {
        
        Car c1 = new Car("K5"2000);
        Car c2 = new Car("A6"8000);
        Car c3 = new Car("BMW3"4000);
        
        Car[] cars = {c1, c2, c3};
        
        Arrays.sort(cars);
 
        for(Car tmp : cars) {
            System.out.println(tmp.getName()+" ");
        }        
    }
}
 
cs

'정렬기준이 없는데?' 라고 생각 들 수 있겠지만 일단 실행시켜보자.

 

역시나 에러가 발생한다.

comparator.Car 가 java.lang.Comparable 로 캐스팅 될 수 없다는 캐스팅 에러.

Arrays.sort 내에서 comparator, comparable 을 사용하다 에러가 발생했다.

Exception in thread "main" java.lang.ClassCastException: comparator.Car cannot be cast to java.lang.Comparable
at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:290)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:157)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at comparator.Main.main(Main.java:19)

 

그럼 이제 Arrays.sort 를 탐구해보자.

 

※ Arrays.sort 

아래는 java.util.Arrays 의 정적메소드 sort.

정확한 파악은 힘들지만 대충 살펴보았을 때 2번째 인자인 Comparator c 값의 유무에 따라 사용되는 메소드가 분기처리 되는 듯 하다.

1) 은 인자가 한개인경우 호출되는 메소드,

2) 는 인자가 두개인 경우 호출되는 메소드

 

1) Arrays.sort(배열) 일 경우 사용되는 method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) {
        int length = high - low;
 
        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }
 
        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);
 
        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }
 
        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }
cs

내부적으로 객체를 Comparable 로 형변환을 한 후 compareTo 메소드를 호출하여 값을 비교(정렬) 하고 있다.

첫번째 인자로 넘겨준 Car 객체는 Comparable(인터페이스) 를 구현하고 있지 않으므로 Comparable 객체로 형변환시 cast exception이 발생한 것이다.

 

그럼 Car 객체가 Comparable 인터페이스를 구현하도록 Car Class 를 아래와 같이 수정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package comparator;
 
public class Car implements Comparable<Car> {
    
    private String name;
    private int price;
    
    public Car (String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
    
    @Override
    public int compareTo(Car c) {
        
        int comPrice = c.getPrice();
        
        return this.price-comPrice;
    }
}
cs

 

다시 한 번 2번의 Main 클래스를 실행해보자

실행 결과는 아래와 같이 성공이다.

※ Car 객체의 compareTo 메소드의 return 부분(30 line : this.price-comPrice)

을 comPrice-this.price 와 같이 반대로 바꾸면 정렬 기준이 바뀐다(오름차순/내림차순).

 

위와 같이 VO 클래스를 Comparable interface 를 구현하도록 수정 후 compareTo 를 overriding 하면 원하는 방식으로 정렬을 시킬 수 있다.

VO 클래스를 이와 같이 매번 구현하여 사용하긴 번거로운데, 다른 방법은 없을까?

 

Arrays.sort 를 다시 한 번 들여다 보자.

2) Arrays.sort(배열, Comparator ?) 일 경우 사용되는 method가 아래와 같이 구현되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;
 
        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }
 
        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);
 
        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (c.compare(src[mid-1], src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
        }
 
        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }
cs

내부적으로 인자값으로 넘어온 Comparator의 compare 메소드를 호출하여 값을 비교(정렬) 하고 있다.

 

Comparable 을 구현한 Car 객체는 원복 시킨 후, Main 클래스를 아래와 같이 수정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package comparator;
 
import java.util.Arrays;
import java.util.Comparator;
 
public class Main {
 
    public static void main(String[] args) {
        
        Car c1 = new Car("K5"2000);
        Car c2 = new Car("A6"8000);
        Car c3 = new Car("BMW3"4000);
        
        Car[] cars = {c1, c2, c3};
        
        System.out.println("sort start!");
        
        Arrays.sort(cars, new Comparator<Car>(){
            @Override
            public int compare(Car c1, Car c2) {
                //return c1.getPrice() - c2.getPrice();
                return c2.getPrice() - c1.getPrice();
            }
        });
        
        for(Car tmp : cars) {
            System.out.println(tmp.getName()+" ");
        }
        
    }
 
}
 
cs

Arrays.sort 의 두번째 인자값으로 Comparator 를 넘겨주었다. (익명클래스로 구현)

 

실행시 결과는 아래와 같다.

 

사족 : Comparable, Comparator를 통해 바라본 인터페이스에 대해..

Arrays.sort 메소드는 내부적으로 파라미터 값으로 받은 객체의 compareTo 메소드 및 Comparable interface 의 compare 메소드를 사용하고 있다.

어떤 자료형의 파라미터 값이 넘어올지 Arrays 의 sort 메소드는 알 수 없지만 파라미터로 넘겨받은 객체 내에 compareTo 가 존재하리라 가정하고 소스가 짜여져 있다.

이처럼 인터페이스는 구현을 강제하며(Car 클래스의 compareTo 메소드) 소스간 결합도(Arrays.sort 와 Car클래스)를 약하게 한다. 또한 내공이 필요 하겠으나, 개발자들은 이를 활용하여 다양한 디자인 패턴의 개발을 할 수 있다..

 

 

 

반응형

#1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> list1 = new ArrayList<String>();
        
list1.add("사과");
list1.add("딸기");
list1.add("키위");
list1.add("바나나");
 
List<String> list2 = list1;
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
 
System.out.println("list1 의 \"사과\" 제거 ");
list1.remove(0);
 
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
cs

1) list1에 사과, 딸기, 키위, 바나나 를 넣었다 (3~6 line)
2) list2에 list1을 넣어주었다 (8 line)
3) list1에서 사과(index : 0) 값을 제거 (13 line)
4) 사과 제거 전 list1, list2 출력 (9, 10 line)
5) list1에 담긴 사과 제거 후 list1, list2 출력 (15, 16 line)

 

위의 코드 실행시 결과는 어떻게 될까?

list1 의 사과 값만 제거 했으므로 list2 엔 사과 값이 존재하고, 출력될까?

 

결과는 다음과 같다. 

list1 에서 사과를 제거했는데 list2 의 사과도 제거되었다.

 

list2 에 list1을 대입한 게 아닌, list2가 list1 의 주소값을 참조하게 했기 때문이다.

List 는 참조형 이기 때문에 값자체가 아닌 주소값을 참조하므로 

#1과 같이 코딩시 위처럼 예상치 못한 결과가 나올 수 있다. (Map 및 Set 또한 마찬가지)

아래와 같이 addAll (List, Set), putAll (Map)을 사용하여 값 자체를 대입시켜 줄 수 있다.

 

 

#2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<String> list1 = new ArrayList<String>();
 
list1.add("사과");
list1.add("딸기");
list1.add("키위");
list1.add("바나나");
 
List<String> list2 = new ArrayList<String>();
list2.addAll(list1);
 
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
 
System.out.println("list1 의 \"사과\" 제거 ");
list1.remove(0);
 
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
cs

#1 코드에서 list2 의 선언 및 list1 의 값을 넣어주는 부분(8~9 line) 만 위와 같이 바꿔보았다.

 

결과는 다음과 같다.

#1과 달리 list2의 사과값이 제거되지 않았다.

 

#3 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
List<Map<StringString>> list1 = new ArrayList<Map<StringString>>();
 
Map<StringString> map1 = new HashMap<StringString>();
map1.put("과일""사과");
map1.put("동물""물고기");
map1.put("지역""서울");
 
Map<StringString> map2 = new HashMap<StringString>();
map2.put("과일""바나나");
map2.put("동물""호랑이");
map2.put("지역""부산");
 
list1.add(map1);
list1.add(map2);
 
List<Map<StringString>> list2 = new ArrayList<Map<StringString>>();
list2.addAll(list1);
 
System.out.println("조작전 list1 : " + list1);
System.out.println("조작전 list2 : " + list2);
System.out.println("=================================");
 
list1.get(0).remove("과일");
 
System.out.println("조작후 list1 : " + list1);
System.out.println("조작후 list2 : " + list2);
cs

1) map1 에 과일:사과, 동물:물고기, 지역:서울 값을 넣는다.

2) map2 에 과일:바나나, 동물:호랑이, 지역:부산 값을 넣는다.

3) list1 에 map1, map2 값을 넣는다.

4) list2 에 list1을 넣는다.

5) list1에 담긴 map1(list1.get(0))의 과일 값을 제거한다

6) 조작전 list1 과 조작 후 list1, list2 를 출력한다.

 

결과는 아래와 같다.

list2에 list1 을 addAll 로 넣어주었는데? 라고 생각할 수 있지만,

list1 이 갖고 있는 map1, map2 값 자체가 아닌 map1, map2 의 주소값만 list2 에 저장되었기 때문이다.

(#2에서 확인한 것 처럼 #3에서 역시, list2.remove(0) 으로 list2에 담겨있는 map1의 주소값을 제거할 경우 list1엔 영향을 주지 않는다)

 

#4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
List<Map<StringString>> list1 = new ArrayList<Map<StringString>>();
 
Map<StringString> map1 = new HashMap<StringString>();
map1.put("과일""사과");
map1.put("동물""물고기");
map1.put("지역""서울");
 
Map<StringString> map2 = new HashMap<StringString>();
map2.put("과일""바나나");
map2.put("동물""호랑이");
map2.put("지역""부산");
 
list1.add(map1);
list1.add(map2);
 
List<Map<StringString>> list2 = new ArrayList<Map<StringString>>();
        
for(Map<StringString> m : list1) {
    Map<StringString> map3 = new HashMap<StringString>();
    map3.putAll(m);
    list2.add(map3);
}
 
System.out.println("조작전 list1 : " + list1);
System.out.println("조작전 list2 : " + list2);
System.out.println("=================================");
 
map2.remove("과일");
 
System.out.println("조작후 list1 : " + list1);
System.out.println("조작후 list2 : " + list2);
cs

#3 코드에서 list2 의 선언 및 list1 의 값을 넣어주는 부분(18~21 line) 만 위와 같이 바꿔보았다.

 

결과는 다음과 같다.

 

* 2중 for문 및 iterator 를 돌려 map 내의 값을 직접 꺼내 map 에 직접 넣어준 후 list 에 add 를 해줘도 된다.

* map 이 아닌 VO의 경우 clone 을 사용하여 vo 값 자체를 복사한 후, list에 넣어 줄 수도 있다.

 

※ deep copy , shallow copy 에 대한 예제와 설명이 정리되어있는 글

https://howtodoinjava.com/java/collections/arraylist/arraylist-clone-deep-copy/

 

※ 참조형 같으면서 기본형 같은 String 자료형에 대한 설명이 잘 되어있는 좋은 글

https://brunch.co.kr/@kd4/1

반응형

[Spring property 두가지 설정 방법]

1. context:property-placeholder 태그 사용

<context:property-placeholder location="classpath:프로퍼티파일명"/>

* 빈팩토리 후처리기로써 빈 설정 메타정보 모두 준비됐을 때 메타정보 자체를 조작 : 빈 설정이 끝난 이후 ${} 표현을 찾아 해당하는 프로퍼티로 치환해준다는 의미인 듯

* ${} 사용 : "${프로퍼티key}" 와 같이 사용

* @Value annotation 에서도 사용이 가능 : @Value("${프로퍼티key}")

 

2. bean 으로 등록

<util:properties id="빈아이디" location="classpath:프로퍼티파일명" />

* Spring 3.0 이상부터 지원

* 프로퍼티 파일 내용을 Properties 타입의 빈으로 생성

* spEL(Spring Expression Language) 사용 : #{빈아이디['프로퍼티Key']} 와 같이 사용

* @Value annotation 에서도 사용이 가능 : @Value("빈아이디['프로퍼티Key']")

 

[Ex]

1. Comm.properties 파일 생성 (resources/sample/properties/Comm.properties)

api.server.url="https://sample.api.com"
db.driverClass="sampleDriverClass"
db.url="sampleDBUrl"
db.username="sampleDBUrl"
db.password="sampleDBUrl"

2. property 설정(Bean 주입, context.xml 설정)

<context:property-placeholder location="classpath:sample/properties/Comm.properties" />
혹은
<util:properties id="properties" location="classpath:sample/properties/Comm.properties" />

※서버 환경별(로컬, 개발, 운영 등) property 파일을 동적으로 다르게 사용하고자 할 경우 이곳 참고

 

3. 스프링설정파일 (~context.xml) 에서의 사용

context:property-placeholder 태그 사용한 property 설정시 :

1
2
3
4
5
6
<bean id=""dataSource" class="~">
   <property name="driverClass" value="${db.driverClass}" />
   <property name="url" value="${db.url}" />
   <property name="username" value="${db.username}" />
   <property name="password" value="${db.password}" />
</bean>
cs

util:properties 와 같이 bean 으로 property 설정시 :

1
2
3
4
5
6
7
<bean id=""dataSource" class="~">
   <property name="driverClass" value="#{properties['db.driverClass']}" />
   <property name="url" value="#{properties['db.url']}" />
   <property name="username" value="#{properties['db.username']}" />
   <property name="password" value="#{properties['db.password']}" />
</bean>
 
cs

 

4. 소스에서의 사용

context:property-placeholder 태그 사용한 property 사용시 :

@Value("${api.server.url}") 

private String apiServerUrl;

 

util:properties 와 같이 bean 으로 property 사용시 :

@Value("#{properties['api.server.url']}") 

private String apiServerUrl;

 

 

[ property 의 형변환 ]

property 값은 기본적으로 String 형태의 Text로 선언 및 사용되는게 default.

String 이 아닌 다른 기타 자료형을 바꿔서 사용해야하는 경우

1. PropertyEditor : 스프링이 default로 해당 에디터를 사용하여 property를 형변환 함 

: boolean, short, int, long, double 등의 기본 자료형 및 Boolean, Short, Integer, Long, Double 등의 wrapper클래스의 오브젝트 타입도 지원

ex) @Value("1.2") double num;

: array 도 사용 가능

ex) @value("1,2,3") int[] arr;

 

* PropertyEditor interface 직접구현하여 사용자가 직접 정의한 형태로 값을 리턴하도록 사용 가능 (Thread safe X)

 

2. ConversionService : 스프링 3.0부터 지원 (Thread safe O)

bean 으로 선언하여 사용.

 

 

공부를 더 해봐야 하겠지만 지금 당장 String 및 기본자료형 정도로만 property 사용하면 되므로

나중에 필요할 때 공부하는걸로..

 

참고 :

토비의스프링 3.1 (도서)

https://whiteship.tistory.com/2563

반응형

* Spring 5.1.2

* jdk 1.8

* jboss/wildfly 9

* jndi 

 

1. xml + aop pointcut 을 사용한 설정 (context xml 설정)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- transactionManager 적용 범위 지정(advice) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true" rollback-for="Exception" />
        <tx:method name="select*" read-only="true" rollback-for="Exception" />
        <tx:method name="insert*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>
 
<!-- transactionManager pointcut  -->
<aop:config>
    <aop:pointcut id="txMethod" expression="execution(* com.sample..*Service.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txMethod" />
</aop:config>
 
<!-- transactionManager bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- datasource bean (jndi 사용) -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/jdbc/AdmJNDI" />
</bean>
     
cs

* aop pointcut 으로 선언 및 설정할 경우 위와 같이 적용할 pointcut 범위를 지정.

(* com.sample..*Service.*(..)) : 모든 접근제한자의 com 패키지 밑의 sample 패키지 밑의 ~Service로 끝나는 모든 클래스 밑의 모든(*) 메소드

* advice 설정에 메소드별로 제약 및 옵션을 걸어 줄 수 있다.

  get* : get으로 시작하는 모든 메소드

  select* : select 로 시작하는 모든 메소드

* read-only = true : insert / update / delete 쿼리를 내부적으로 실행시 exception 을 뱉는다.

* roll-backfor = Exception : Exception (Exception 밑의 모든 Exception 포함) 이 발생할 경우 rollback 처리. (no rollback for 옵션으로 특정 Exception이 발생시 rollback 처리를 하지 않게 처리 가능)

propagation : 트랜잭션의 전파속성으로써 메소드 내에서 다른 메소드를 사용할 때 하나의 트랜잭션으로 묶을지, 별도의 트랜잭션으로 분류할지 등 과 같은 설정을 지정하는 옵션. default 값이 required 로 알고 있어 위와 같이 선언할 필요는 없는 걸로 알고 있다.. 여러가지 설정이 있는데 나중에 정리하는걸로. 

 

>> com.sample..*Service 밑의 get*, insert*, delete*, select*, update* 이름을 가진 모든 메소드는 위의 트렌잭션 설정에 의해 관리된다.

 

 

2. annotation 을 사용한 설정

1
2
3
4
5
6
7
8
9
10
11
12
<!-- transactional annotation 설정 사용 -->
<tx:annotation-driven transaction-manager="txManager" />
 
<!-- transactionManager bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- datasource bean (jndi 사용) -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/jdbc/AdmJNDI" />
</bean>
cs

* txManager 와 관련된 database 설정, transaction manager bean 선언 부분은 1번과 동일.

 

>> Transaction 으로 관리하고 싶은 메소드위에 @Transactional 어노테이션을 달아주어 사용.

 

 

* 주의사항

1. @Transactional 은 public 메소드에서만 정상 작동한다.

 

2. @Transactional 을 달아놓은 메소드가 동일한 클래스 내의 다른 메소드에 의해 호출된다면 트랜잭션이 정상 작동하지 않는다.

ex: 퍼사드 패턴이나 템플릿 패턴처럼 여러 메소드를 내부적으로 묶어 사용하고 있는 메소드가 있다면 구성요소 메소드에 @Transactional 를 달지 않고 구성요소를 묶고 있는 상위개념의 메소드에 @Transactional 을 달아주어야 한다. 구성요소 메소드에 @Transactional 을 달아 주어 트랜잭션으로 관리 할 경우 rollback 이 정상적으로 작동하지 않는 경우가 발생한다.

 

위 내용과 같은 경우가 아래에 정리되어 있다.

https://woowabros.github.io/experience/2019/01/29/exception-in-transaction.html

propagation 속성이 required 인 경우, 트랜잭션안에서 호출되는 메소드가 트랜잭션으로 같이 묶이게 되어 예상치 못한 결과가 나올 수 있다는 내용이다.

※ required propagation 속성은 트랜잭션이 이미 존재하면 append를 하고, 트랜잭션이 존재하지 않다면 새로 생성한다. (공식 doc)

 

3. Spring Transaction 은 기본적으로 unchecked Exception (RuntimeException) 만 관리하며 checked Exception (IOException, SQLException 등) 은 관리하지 않는다.

처리방법 1: @Transactional(rollbackFor=Exception.class) 와 같이 설정하여 모든 Exception 발생시 rollback 이 발생하게 처리하거나(unchecked Exception, checked Exception 모두 Exception.class 밑에 있다.)

처리방법 2: checked Exception 이 발생할 가능성이 있는 부분을 try ~ catch (SQLException se){throw new RuntimeException(); } 과 같이 처리(checked Exception 발생시 unchecked Exception 으로 예외를 바꾸어 던지게 처리)하여 Transaction의 관리대상으로 묶어버릴 수 있다.

 

4. pointcut 을 사용한 Transaction 설정과 어노테이션을 사용한 Transaction 설정을 동시에 사용하게 될 경우, 어노테이션이 우선적용(precedence)되는 것 같다. (https://stackoverflow.com/questions/32057910/custom-spring-aop-around-transactional/33509777#33509777)

 

 

* 회고.

몇일 전, 정상적으로 트랜잭션이 관리되고 있다고 믿고 있던 내 소스가 특정 케이스에서 rollback 이 이뤄지지 않고 있다는 걸 발견했다.멘탈폭발

개발중인 소스는 4번과 같이 두 설정 모두를 사용하고 있던터라 advisor 와 어노테이션을 함께 사용하여 발생하는 문제인가 싶어 트랜잭션 관련 설정을 이렇게도 바꿔보고 저렇게도 바꿔보았다.

(stackoverflow 에서 두가지 설정 모두를 사용하여 충돌이 발생할 경우 order 설정에 의한 우선순위에 의해 트랜잭션이 관리되고, order 설정이 존재하지 않는다면 마지막에 선언된 설정을 따른다는 글을 봤던게 설정에 의심을 갖게된 결정적인 계기였다. (답변자가 부정확한 답변을 했거나, 내 해석이 잘못됐거나, 개발환경 차이에 의한 차이거나... 그중 하나겠지..))

퇴근 후 몇일간 탐구삽질해본 결과

동일한 프로젝트 내의 타 메소드에선 트랜잭션 관리가 정상적으로 되고 있다는 걸 알 수 있었고, 주말까지 잡아먹은 끝에 4번의 문제가 아닌, 2번의 문제였다는 걸 깨닫게 되었다..(어설프게 디자인 패턴 써보겠다고 소스 리팩토링 하면서 스스로 만들어낸 참사)

애초에 @Transactional 자체가 정상 작동할 수 없는 케이스에서 rollback이 이뤄지지 않는다고 다른 부분들을 전부 의심하기 시작한 셈이다.

덕분에? 관련 docs 도 많이 찾아 읽어보고, transaction 관련 설정부분 및 기본이자 핵심 부분들을 조금이나마 더 깊이있게 공부할 수 있었던 의미 있는 삽질의 시간이었다.

 

* 3번의 경우 위와 같이 설명되어 있는 글들을 여럿 봤는데, 내 설정과 코드에선 굳이 위처럼 처리하지 않더라도(단순히 @Transactional 어노테이션만 붙여도) rollback 이 잘 이루어 지고 있다.. 스프링 버전이나 jdbc 트랜잭션매니저 버전이 올라가면서 SQLException 을 스프링이 트랜잭션 관리 대상에 포함을 시킨건지.. (잘 모르겠으니 다시 구글링 좀 해보고, 테스트 코드도 작성해봐야겠다..)

 

 

반응형

[JSTL이란]

JSTL 은 JSP(Java Server Pages) Standard Tag Library의 약자로써, jsp 어플리케이션의 핵심기능을 캡슐화한 유용한 태그 모음.

 

tutorial 사이트 :

https://www.tutorialspoint.com/jsp/jsp_standard_tag_library.htm

 

<JSTL 사용을 위한 taglib 선언>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

사용하고자 하는 태그라이브러리(taglib)의 prefix 를 각각 선언

 

 

<JSTL tag 별 용도 및 설명>

1.c tag : 코어

c:if : 조건문 

c:choose c:when c:otherwise : switch case default 조건문

c:forEach : 반복문

c:set : 변수 할당

c:out : 출력

2.fn tag : 길이반환 등의 함수

fn:length : 길이 반환

fn:substring : 문자열 자르기

3.fmt tag : format 변환용으로 사용

아래 url 참고

https://www.javatpoint.com/jstl-formatting-tags

 

 

<사용 예>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<c:if test="${ not empty dataList}">
     <c:forEach var="list" items="${dataList}" varStatus="status">
        <tr class="dataTr tmpTr" style='cursor:pointer;' onclick="goInfo('${list.seq}')">
            <td>${list.start_page_limit + status.count}</td>
            <td>${list.seq}</td>
            <td>
            <c:choose>
               <c:when test="${fn:length(list.memo)>20}">
                  <c:out value="${fn:substring(list.memo, 0, 19)}"/>...
               </c:when>
               <c:otherwise>
                  <c:out value="${list.memo}"/>
               </c:otherwise>
            </c:choose>
            </td>
            <td>
            <c:set var="nm" value="${fn:split(list.user_name, '^')}" />
            <c:forEach var="obj" items="${nm}" varStatus="idx">
              ${obj}<c:if test="${!idx.last}">,<br></c:if>
            </c:forEach> 
            </td>
        </tr>
    </forEach>
</c:if>
cs

1 line : 서버로부터 받아온 dataList 가 비어있지 않다면 2~23 라인 실행

2 line : 반복문

3 line : 서버에서 받아온 dataList 를 list 변수에 담아 for 문 내에서 사용. varStatus 는 index 와 관련되어 사용이 가능한 속성

4 line : varStatus.count로 순번 출력(1부터 출력) (참고: varStatus.index는 0부터 출력)

7 ~ 14 line : if else 구문

8 line : list에서 가져온 memo 의 길이가 20 보다 큰 경우

9 line : 0~19번째 문자까지 출력 후 ... 붙이기 (말줄임)

17 line : nm 변수에 list 에서 가져온 user_name 을 ^ 문자 기준으로 split 하여 저장

18 line : nm 변수를 obj 변수에 담아 for 문 내에서 사용

19 line : 마지막이 아닌 경우 (!varStatus.last) ,<br> 을 붙임 (반대로 다시말해 마지막인 경우 ,<br> 을 붙이지 않음) 

 

 

< foreach 의 varStatus >

<c:forEach var='list' items='${dataList}' varStatus='status'>

   ${status.current} // 현재아이템

   ${status.index}   // 0부터 시작하는 인덱스

   ${status.count}   // 1부터 시작하는 카운트

   ${status.first}   // 현재 인덱스가 첫번째 인덱스인지 판별

   ${status.last}    // 현재 인덱스가 마지막 인덱스인지 판별

   ${status.begin}   // 반복문 시작 값

   ${status.end}     // 반복문 끝 값

   ${status.step}    // 반복문 증가 값

</c:forEach>

 

참고 : https://www.javatips.net/blog/jstl-last-using-varstatus

 

 

< 주의사항 > 

페이지 로드 순서(웹언어들의 동작 및 실행 순서(Load Sequence On Web))에 주의하자..

JAVA > JSTL > HTML > JavaScript(jQuery)

 

 

해당 부분은 따로 정리해서 포스팅하겠다.

 

반응형

POI (Excel) + Spring + JSP

 

POI lib 을 사용한 excel 파일 생성 및 다운로드

 

 

1. dependency 추가(HSSF 예제)

1
2
3
4
5
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.15</version>
</dependency>
cs

* HSSF이외에 XSSF, SXSSF 라이브러리 존재. 

HSSF : excel 의 .xls 파일 포맷을 위한 POI

XSSF : .xls 및 .xlsx 파일 포맷을 위한 POI

SXSSF : 이미지 및 대용량 엑셀 데이터를 위한 POI

SXSSF 의 경우 HSSF, XSSF에 비해 성능이 떨어지는 거라는 글을 봤음, 대용량 처리가 아닌 이상 XSSF 혹은 HSSF 사용

 

2. 엑셀 파일 생성(HSSF 사용 예제)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Override
    public HSSFWorkbook listExcelDownload(VO param) throws Exception {
        
        
        HSSFWorkbook workbook = new HSSFWorkbook();
        
        HSSFSheet sheet = workbook.createSheet("엑셀시트명");
        
        HSSFRow row = null;
        
        HSSFCell cell = null;
        
        param.setPager(false);
        param.setNullText(NULL_TEXT);
        param.setSeparator(DELI_EXCEL);
        List<VO> list = Dao.selectList(param);
        
        row = sheet.createRow(0);
        String[] headerKey = {"칼럼1""칼럼2""칼럼3""칼럼4"};
        
        for(int i=0; i<headerKey.length; i++) {
            cell = row.createCell(i);
            cell.setCellValue(headerKey[i]);
        }
        
        for(int i=0; i<list.size(); i++) {
            row = sheet.createRow(i + 1);
            StbcsTaskHstVO vo = list.get(i);
            
            cell = row.createCell(0);
            cell.setCellValue(vo.getEx1());
            
            cell = row.createCell(1);
            cell.setCellValue(vo.getEx2());
            
            cell = row.createCell(2);
            cell.setCellValue(vo.getEx3());
            
            cell = row.createCell(3);
            cell.setCellValue(vo.getEx4());
 
        }
        
        return workbook;
    }
    
cs

* HSSF lib 사용시

HSSFWorkbook 생성 > HSSFWorkbook에 HSSFSheet 생성 > HSSFSheet에 HSSFRow 생성 > HSSFRow에 HSSFCell 생성

* XSSF lib 사용시

흐름은 위와 동일, HSSF을 XSSF로만 바꿔주면 됨(ex: HSSFWorkbook >> XSSFWorkbook).

 

 

3. Controller (엑셀 다운로드)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping(value="/exceldownload.do")
    public void excelDownload( HttpServletRequest request ,HttpServletResponse response ,HttpSession session, VO param) throws Exception {
        
        OutputStream out = null;
        
        try {
            HSSFWorkbook workbook = Service.listExcelDownload(param);
            
            response.reset();
            response.setHeader("Content-Disposition""attachment;filename=stbcs_history.xls");
            response.setContentType("application/vnd.ms-excel");
            out = new BufferedOutputStream(response.getOutputStream());
            
            workbook.write(out);
            out.flush();
            
        } catch (Exception e) {
            logger.error("exception during downloading excel file : {}", e);
        } finally {
            if(out != nullout.close();
        }    
    }
cs

 

4. jsp

1
2
3
4
5
6
7
8
9
/* 엑셀다운로드 */
function downloadExcel(){
 
    var f = document.frmSearch;
    f.action = "/exceldownload.do";
    f.submit();
    
    return;
}
cs

 

 

 

 

반응형

1. 변화하는 기능을 별도의 interface로 분리(캡슐화)

2. 각각의 기능(interfaceImple)들이 인터페이스를 구현(implements)

3. 기능을 사용하는 객체(class) 내에서 기능(interface)을 멤버변수로 포함

4. 기능을 사용하는 객체(class) 내에서 기능(interfaceImple)을 주입받는 setter를 선언하여 각각의 객체(class)가 다른 기능(interfaceImple) 을 사용할 수 있게 설계

 

* 무조건적인 상속은 소스의 중복을 야기, 소스 수정시 구현체들의 소스 수정을 야기

 

[ex]

[Shout interface]

소리지르기 비명지르기 기능이 있는 Shout 인터페이스 작성.

1
2
3
4
5
6
7
package design_pattern.strategy;
 
public interface Shout {
    public void yell();
    public void scream();
}
 
cs

 

[Human class]

Shout 인터페이스를 구현한 Human 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package design_pattern.strategy;
 
public class Human implements Shout{
    
    @Override
    public void yell() {
        System.out.println("으악");
    }
 
    @Override
    public void scream() {
        System.out.println("꺄");
    }
    
}
 
cs

 

[Dog class]

Shout 인터페이스를 구현한 Dog 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package design_pattern.strategy;
 
public class Dog implements Shout{
 
    @Override
    public void yell() {
        System.out.println("왈");
    }
 
    @Override
    public void scream() {
        System.out.println("왈");
    }
    
}
 
cs

 

[Throat class]

소리지르기, 비명지르기 기능을 갖고 있는 Shout 인터페이스를 멤버변수로 두고 있는 Throat 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package design_pattern.strategy;
 
public class Throat {
    
    Shout s;
    
    public void setThroat(Shout s) {
        this.s = s;
    }
    
    public void func1() {
        s.yell();
    }
    
    public void func2() {
        s.scream();
    }
    
}
 
cs

 

[Main class(client)]

기능을 사용하는 클라이언트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package design_pattern.strategy;
 
public class Main {
    public static void main(String args[]) {
        
        Throat throat = new Throat();
        
        throat.setThroat(new Human());
        throat.func1();
        throat.func2();
        
        System.out.println("------------------");
        
        throat.setThroat(new Dog());
        throat.func1();
        throat.func2();
        
    }
}
 
cs

 

[결과]

클라이언트 단에서 setter를 사용하여 클래스(기능)를 변경하여 사용 가능하다.

 

정리가 잘 되어있는 글

참고 도서

 

 

반응형

[클라이언트 ip 가져오기]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static String getIp(HttpServletRequest request){
    String result = null;
    
    result = request.getHeader("X-Forwarded-For");
    
    if (result == null || result.length() == 0 || "unknown".equalsIgnoreCase(result)) {
        result = request.getHeader("Proxy-Client-IP");
    }
    if (result == null || result.length() == 0 || "unknown".equalsIgnoreCase(result)) {
        result = request.getHeader("WL-Proxy-Client-IP");
    }
    if (result == null || result.length() == 0 || "unknown".equalsIgnoreCase(result)) {
        result = request.getHeader("HTTP_CLIENT_IP");
    }
    if (result == null || result.length() == 0 || "unknown".equalsIgnoreCase(result)) {
        result = request.getHeader("HTTP_X_FORWARDED_FOR");
    }
    if (result == null || result.length() == 0 || "unknown".equalsIgnoreCase(result)) {
        result = request.getRemoteAddr();
    }
    
    return result==null?"":result; 
}
 
cs

 

[서버 ip 가져오기]

1
2
3
4
5
6
7
8
9
 public static String getIp(){
    String result = null;
    try {
        result = InetAddress.getLocalHost().getHostAddress();
    } catch (UnknownHostException e) {
        result = "";
    }
   return result; 
}
cs

 

찾다보니 아래와 같은 글도 찾게 되었다.

https://pkgonan.github.io/2018/06/InetAddress-getLocalHost

 

반응형

+ Recent posts