반응형
  • 컬랙션 객체임을 JPA 에 알려주는 Annotation.
    @Entity 
    public class Person { 
    	@Id 
        private Long id; 
        private String email; 
        
        @ElementCollection 
        @CollectionTable( name = "address", joinColumns = @JoinColumn(name = "person_id") ) 
        List<AddressInfo> addressInfoList = new ArrayList<>(); 
    }
  • Entity 와 라이프 싸이클을 같이 하며 독립적으로 사용 불가능 하다.
  • 부모 Entity가 삭제될 경우 같이 삭제된다. (실제 클래스에 cascade 를 설정하는 옵션이 없다.)
  • ElementCollection의 Fetch 전략은 기본이 Lazy 이다.
  • 실제 테이블은 FK 를 이용해서 생성된다.
    Hibernate: create table address (person_id bigint not null, address1 varchar(255), address2 varchar(255), zip_code varchar(255))
    Hibernate: create table person (id bigint not null, email varchar(255), primary key (id))
    Hibernate: alter table address add constraint FK81ihijcn1kdfwffke0c0sjqeb foreign key (person_id) references person
  • CollectionTable Annocation 을 사용하지 않을 경우에는 다음과 같이 테이블이 생성된다.
    Hibernate: create table person_address_info_list (person_id bigint not null, address1 varchar(255), address2 varchar(255), zip_code varchar(255))
728x90
반응형
반응형

JPA 가 엔티티 데이터에 접근하는 방식을 지정한다.

1. AccessType.FIELD : 필드에 직접 접근한다.

@Access(AccessType.FIELD)
private String address1;

2. AccessType.PROPERTY : 프로퍼트로 접근한다. 

@Access(AccessType.PROPERTY)
public String getAddress2() {
	return address1 + address2;
}

3. AccessType 이 지정되지 않은 경우는 @Id 위치에 따라 지정된다.

@Entity
public class OrderInfo {
    @Id
    private Long id;
    private String address1;
    @Transient
    private String address2;

    @Access(AccessType.PROPERTY)
    public String getAddress2() {
        return address1 + address2;
    }

    public void setAddress2(String address2) {
        this.address2 = address2;
    }
}

- @Id 위치가 필드에 있기때문에 기본적으로 AccessType.FIELD 가 적용된다. AccessType.PROPERTY를 같이 적용하기 위해서는 메소드 위에 AccessType.PROPERTY 를 넣어주면 된다.

4. 기타 설명들

@Access is used to specify how JPA must access (get and set) mapped properties of the entity. If access type is set to FIELD, the values will directly be read/set on the field, bypassing getters and setters. If set to PROPERTY, the getters and setters are used to access the field value.

FIELD 로 정의하면 다이렉트로 field를 read/set 하고 PROPERTY로 설정하면 getter, setter 메소드를 통해서 접근한다.
https://stackoverflow.com/questions/19264871/what-is-the-use-of-the-access-annoation-in-jpa-means-at-the-entity-level

 

If you use field-based access, your JPA implementation uses reflection to read or write your entity attributes directly. It also expects that you place your mapping annotations on your entity attributes. If you use property-based access, you need to annotate the getter methods of your entity attributes with the required mapping annotations. Your JPA implementation then calls the getter and setter methods to access your entity attributes.

5 reasons why you should use field-based access
Better readability of your code
Omit getter or setter methods that shouldn’t be called by your application
Flexible implementation of getter and setter methods
No need to mark utility methods as *@Transient*
Avoid bugs when working with proxies

https://thorben-janssen.com/access-strategies-in-jpa-and-hibernate/

 

 

728x90
반응형
반응형

Spring Data Jpa 를 사용하게 되면서 전에 쓰지 않았던 paging 기능에 대해서 이야기 해보려 한다. 

Domain 을 조회하는 Repository interface를 만들때에 JpaRepository 를 상속해서 사용하는 경우가 많다. 
이 JpaRepository 를 살펴보면 내부에 이렇게 구현이 되어있다.  

public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor { 
..... 
} 

그리고 다시 PagingAndSortingRepository 를 살펴보면 다음과 같다.

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { 
    Iterable findAll(Sort var1); 

    Page findAll(Pageable var1); 
} 

위에서 볼수 있듯이 Page를 리턴해주는 메소드가 이미 포함되어있다. 

 

그럼 간단히 검색 조건 없이 모든 Data를 검색하는 코드를 작성해보자

 

AccountRestController.java

@GetMapping
public Page<AccountResDto> getPageableAccountList(Pageable pageable){
	return accountService.findAll(pageable);
}

AccountServiceImpl.java

@Override
public Page<AccountResDto> findAll(Pageable pageable) {
  Page<Account> accounts = accountRepository.findAll(pageable);
  return accounts.map(AccountResDto::new);
}

 

http://localhost:9000/api/v1/accounts 로 요청을 하면 다음과 같이 응답을 받을수 있다.

{
    "content": [
        {
            "loginId": "admin",
            "username": "admin",
            "email": "admin@spring.com"
        },
        {
            "loginId": "auser01",
            "username": "user",
            "email": "user01@spring.com"
        },
        {
            "loginId": "guest",
            "username": "guest",
            "email": "guest@spring.com"
        },
        {
            "loginId": "buser02",
            "username": "user",
            "email": "user02@spring.com"
        },
        {
            "loginId": "cuser03",
            "username": "user",
            "email": "user03@spring.com"
        },
        {
            "loginId": "duser04",
            "username": "user",
            "email": "user04@spring.com"
        },
        {
            "loginId": "euser05",
            "username": "user",
            "email": "user05@spring.com"
        },
        {
            "loginId": "fuser06",
            "username": "user",
            "email": "user06@spring.com"
        },
        {
            "loginId": "guser07",
            "username": "user",
            "email": "user07@spring.com"
        },
        {
            "loginId": "huser08",
            "username": "user",
            "email": "user08@spring.com"
        },
        {
            "loginId": "iuser09",
            "username": "user",
            "email": "user09@spring.com"
        },
        {
            "loginId": "juser10",
            "username": "user",
            "email": "user10@spring.com"
        }
    ],
    "pageable": {
        "sort": {
            "sorted": false,
            "unsorted": true,
            "empty": true
        },
        "offset": 0,
        "pageSize": 20,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": true,
    "totalPages": 1,
    "totalElements": 12,
    "number": 0,
    "size": 20,
    "sort": {
        "sorted": false,
        "unsorted": true,
        "empty": true
    },
    "first": true,
    "numberOfElements": 12,
    "empty": false
}

리턴 결과를 잘 보면 totalPages 는 1, data 개수는 12, size 는 20 으로 되어있다. 

 

그리고 정렬된 결과를 얻고 싶으면 다음과 같이 파라메터를 추가해주면 된다.

 

http://localhost:9000/api/v1/accounts?sort=loginId,DESC

 

이렇게 하면 loginId 로 정렬된 형태의 결과를 받을수 있다. (그런데 주의할점은 컴마(,) 이후에 공백이 포함되어 있으면 에러가 난다 -_-)

 

그리고 size 도 지정할수 있는데 위 결과는 지정을 하지 않아서 기본 default 로 20 으로 설정된 경우이다.

 

마찬가지로 파라메터에 size 값을 지정해주면 지정한 값으로 size 가 정해지며 그에 따른 totalPages 값도 달라지게 된다.

 

위 코드가 포함된 소스는 아래 주소에서 볼수 있다. (단, 계속 수정을 하기 때문에 소스가 완전히 일치하지 않을수 있다. )

 

https://github.com/blusky10/study_spring

 

blusky10/study_spring

Contribute to blusky10/study_spring development by creating an account on GitHub.

github.com

 

728x90
반응형
반응형

Spring Security 를 적용하는 내용을 처음부터 차근차근 정리를 해보려고 한다. 

목표는 Spring Security 를 공부하면서 각각의 기능들을 적용해보는것이다. 진행하다보면 Spring Security 뿐만 아니라 다른 내용들도 점점 추가될것 같다. 다 만들고 나서는 git에 소스를 공유할 생각이다. ^^;; 언제가 될지는 잘 모르겠다.


환경 : java 1.8, Spring Boot 1.5.3 Release, Maria DB, JPA, gradle


build.gradle

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
buildscript {
    ext {
        springBootVersion = '1.5.3.RELEASE'
    }
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
 
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
 
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
 
repositories {
    jcenter()
    mavenCentral()
}
 
 
dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-security')
 
    testCompile('org.springframework.boot:spring-boot-starter-test')
 
    runtime ('org.mariadb.jdbc:mariadb-java-client')
}
cs



아직 초반이어서 라이브러리가 몇개 없다. spring-boot 에서 사용하는 jpa, security, test, web 라이브러리가 끝이다. 그리고 DB를 사용하기 위한 mariadb client까지만 추가되어있다.


도메인.

Account.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Account {
    @Id
    private String loingId;
 
    private String username;
 
    private String password;
 
    private String email;
 
    private boolean enabled;
}
cs


Account 도메인이 있다. 만들기는 더 여러개 만들어놓았는데 우선은 이것만 필요해서 적었다. 위 Account.java 소스에는 getter/setter 메소드는 삭제하고 올렸다. (너무 길어서)


다음은 Account 관련 Service와 Repository 이다. 


AccountRepository.java

1
2
public interface AccountRepository extends JpaRepository<Account, String> {
}
cs


AccountService.java

1
2
3
4
public interface AccountService {
    Account get(String loginId);
}
 
cs


AccountServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
@Service
public class AccountServiceImpl implements AccountService {
 
    @Autowired
    private AccountRepository accountRepository;
 
    @Override
    public Account get(String loginId) {
        return accountRepository.findOne(loginId);
    }
}
cs


Repository를 다이렉트로 호출해도 되긴 하지만 아무래도 구조상 service 레이어가 있는게 맞다고 판단해서 repository는 Service 에서 호출하도록 한번 감쌌다. 지금은 간단히 호출만 하지만 나중에는 로직도 들어갈것이 예상된다.


CustomUserDetailService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class CustomUserDetailService implements UserDetailsService{
 
    @Autowired
    private AccountService accountService;
 
    @Override
    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
 
        Account account = accountService.get(loginId);
 
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
 
        User user = new User(account.getLoingId(), account.getPassword(), grantedAuthorities);
 
        return user;
    }
}
cs


UserDetailsService에 있는 loadUserByUsername 메소드를 제정의 해서 UserDetails 객체를 정의해준다. UserDetails는 사용자의 인증정보를 담고있다. 위에서 Account Service에서 loginId로 정보를 조회해서 가져온 id, password, 권한(아직 미구현으로 객체만 생성했다) 정보를 user로 생성해주고 있다.




ResourceSecurityConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableGlobalAuthentication
public class ResourceSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private CustomUserDetailService customUserDetailService;
 
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.customUserDetailService);
    }
}
 
cs


WebSecurityConfigurerAdapter에 있는 init 메소드를 사용해서 AuthenticationManangerBuilder에 userDetailService를 내가 새로 만든 CustomUserDetailService를 사용하겠다고 설정해준다.


StudySpringApplication.java

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class StudySpringApplication {
 
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(StudySpringApplication.class);
 
        app.run(args);
    }
}
 
cs


application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
  port: 9000
 
spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/holmes
    username: root
    validation-query: select 1
    test-while-idle: true
    time-between-eviction-runs-millis: 300000
    test-on-borrow: false
 
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQLDialect
        default_schema: holmes
cs


이제 어플리케이션을 실행해보면 로그인 페이지가 나온다. 





위에 소스와 약간 차이가 있을수 있지만 아래 Git repository 에 가면 소스를 확인할 수 있다. 


https://github.com/blusky10/study_spring


728x90
반응형
반응형

아직 사용이 익숙하지 않은 IntelliJ 에서 작업을 하다가 JPA의 Entity로 되어 있는 도메인들을 못찾는 현상이 발생했다. 


src/main/generated 라는 폴더 안에 Qdomain 들이 실제 존재하고 있는 상황인데 build 를 돌리면 찾지 못했다. 


에러 내용은 위와 같이 cannot find symbol, class QCode. 저 캡쳐는 한 부분만 캡쳐를 했지만 실제로는 모든 Qdomain을 찾지 못한다고 에러가 났다. 


그래서 구글링을 해보니 generated 폴더를 패스에 추가를 해줘야 한다는 것을 알았다. 

IntelliJ 에서 File>Project Structure>Modules 에 들어가면 아래와 같이 화면이 구성되어있다. 

위에 화면에서는 이미 Source Folder 에 src\main\genereated 폴더가 추가한 후의 캡쳐이다. 실제로는 우측에 Source Folders 에 src\main\java 만 있었다. 실제로 추가하는 방법은 위 화면에서 보이는 좌측 트리에 src 부분을 펼쳐 보면 genreated 폴더가 보인다. 거기에서 마우스 우클릭을 하면 아래와 같이 메뉴가 나온다. 거기에서 Sources를 선택을 하면 우측 Source Folders 에 추가가 된다. 


이렇게 하고 난 후 build를 실행하게 되면 에러 없이 정상적으로 success 가 된다. 


728x90
반응형

'Development > Tools' 카테고리의 다른 글

VirtualBox 에서 Ubuntu Server 폴더 공유하기  (0) 2019.04.30
Virtual Box 에서 확장 패키지 설치.  (0) 2019.03.11
IntelliJ 단축키  (0) 2016.01.07
ConEmu 에 Putty 설정하기  (0) 2015.10.30
Atom proxy 설정  (0) 2015.08.26
JPA
반응형

Persistence Context 특징

- 1차 캐시

- 동일성 보장

- 트랜잭션을 지원하는 쓰기 지연 (transaction write-behind)

- 변경감지(dirty checking)

- 지연로딩


조회

- 조회시에 1차캐시에서 식별자 값으로 entity 조회. 없으면 DB에서 조회


등록

- persist 를 실행하면 1차 캐시에 저장 되고 transaction writer-behind에 쿼리를 저장해둔다.

- commit 시점에 transaction writer-behind에 있는 쿼리를 실행함.


수정 

- 1차 캐시에 Entity가 저장될 시점에 최초상태의 스냅샷을 같이 저장한다. 

- transaction writer-behind 에서 flush 시점에 스냅샷과 entity를 비교해서 변경된 entity를 찾는다.

728x90
반응형

'Development > Java' 카테고리의 다른 글

spring Cache  (0) 2015.12.05
Spring propagation  (0) 2015.12.01
[JPA]Entity 생명주기  (0) 2015.08.12
Mybatis 동적쿼리 사용시 NuberFormatException:For input String 해결방법  (0) 2014.10.30
util.Date vs sql.Date 차이  (0) 2014.07.01
JPA
반응형

1. new/transient : persistence context와 무관

2. managed : persistence context에 저장된 상태

3. detached : persistence context에 저장되었다가 분리된 상태

4. removed : 삭제된 상태


728x90
반응형

+ Recent posts