네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'2022/05'에 해당되는 글 12건


반응형

jpql delete query with join condition

DELETE FROM Post p
 WHERE p IN 
 (SELECT post FROM Project project
  JOIN project.posts post
  WHERE project.id = post.project.id)

sql delete query with join condition

DELETE posts
FROM posts
INNER JOIN projects ON projects.project_id = posts.project_id
WHERE projects.client_id = :client_id
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

JPA에서 대량의 데이터를 삭제할 때 주의할 점이 있습니다. 결론은 @Query 어노테이션을 사용하여 직접 삭제 쿼리를 작성한다.

 

예제

의존 관리는 Gradle를 사용하고 코드 간결성을 위해 lombok을, 테스트 프레임워크로 Spock을 사용하였다.

 

build.gradle

apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-web')
    runtime('com.h2database:h2')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.spockframework:spock-core:1.1-groovy-2.4')
    testCompile('org.spockframework:spock-spring:1.1-groovy-2.4')
}

application.yml 에 설정값을 추가한다.

spring:
  jpa:
    show-sql: true

사용할 엔티티 클래스는 Customer, Shop, Item이다.

// Customer.java
@Entity
@Getter
@NoArgsConstructor
public class Customer {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    public Customer(String name) {
        this.name = name;
    }
}

// Shop.java
@Getter
@NoArgsConstructor
@Entity
public class Shop {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String address;

    @OneToMany(mappedBy = "shop", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Item> items = new ArrayList<>();

    public Shop(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public void addItem(Item item){
        if(this.items == null){
            this.items = new ArrayList<>();
        }

        this.items.add(item);
        item.updateShop(this);
    }
}

// Item.java
@Getter
@NoArgsConstructor
@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private long price;

    @ManyToOne
    private Shop shop;

    public Item(String name, long price) {
        this.name = name;
        this.price = price;
    }

    public void updateShop(Shop shop){
        this.shop = shop;
    }
}

이 엔티티 클래스를 다룬 JpaRepository를 구현한 repository를 생성하겠습니다.

// CustomerRepository.java
public interface CustomerRepository extends JpaRepository<Customer, Long>{

    @Modifying
    @Transactional
    long deleteByIdIn(List<Long> ids);

    @Transactional
    @Modifying
    @Query("delete from Customer c where c.id in :ids")
    void deleteAllByIdInQuery(@Param("ids") List<Long> ids);
}

// ShopRepository.java
public interface ShopRepository extends JpaRepository<Shop, Long> {

    @Transactional
    @Modifying
    long deleteAllByIdIn(List<Long> ids);

    @Transactional
    @Modifying
    @Query("delete from Shop s where s.id in :ids")
    void deleteAllByIdInQuery(@Param("ids") List<Long> ids);
}

// ItemRepository.java
public interface ItemRepository extends JpaRepository<Item, Long> {

    @Transactional
    @Modifying
    @Query("delete from Item i where i.shop.id in :ids")
    void deleteAllByIdInQuery(@Param("ids") List<Long> ids);
}

첫번째 메서드인 deleteAllByIdIn은 JpaRepository에서 제공하는 delete 메서드를 활용한 것이다.

두번째 메서드인 deleteAllByIdInQuery는 @Query를 사용하여 직접 delete 쿼리를 사용한 것이다.

 

1. 다른 엔티티와 관계가 없는 엔티티 삭제

다른 엔티티와 관계가 없는 Customer 엔티티 삭제 기능을 테스트한 예제이다.

@SpringBootTest
class CustomerRepositoryTest extends Specification {

    @Autowired
    private CustomerRepository customerRepository;
    
    def "Customer in delete" () {
        given: // 100개의 데이터를 DB에 insert
        for (int i = 0; i < 100; i++) {
            customerRepository.save(new Customer(i + "님"))
        }
        
        when: // 3개의 ID 조건으로 delete
        customerRepository.deleteByIdIn(Arrays.asList(1L, 2L, 3L))
        
        then:
        println "======THEN====="
        customerRepository.findAll().size() == 97
    }
}
Hibernate: insert into customer (id, name) values (null, ?)
Hibernate: insert into customer (id, name) values (null, ?)
Hibernate: insert into customer (id, name) values (null, ?)
...
Hibernate: insert into customer (id, name) values (null, ?)
Hibernate: select customer0_.id as id1_0_, customer0_.name as name2_0_ from customer customer0_ where customer0_.id in (?, ?, ?)
Hibernate: delete from customer where id = ?
Hibernate: delete from customer where id = ?
Hibernate: delete from customer where id = ?
=====THEN=====

in 으로 조회하는 쿼리가 처음으로 실행된다.

id별로 각각 delete가 실행된다.

 

public abstract class AbstractJpaQuery implements RepositoryQuery {
    // ... 생략
    
    @Nullable
    public Object execute(Object[] parameters) {
        return this.doExecute(this.getExecution(), parameters);
    }

    @Nullable
    private Object doExecute(JpaQueryExecution execution, Object[] values) {
        JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor(this.method.getParameters(), values);
        Object result = execution.execute(this, accessor);
        ResultProcessor withDynamicProjection = this.method.getResultProcessor().withDynamicProjection(accessor);
        return withDynamicProjection.processResult(result, new AbstractJpaQuery.TupleConverter(withDynamicProjection.getReturnedType()));
    }
    // ... 생략
}
public abstract class JpaQueryExecution {
    private static final ConversionService CONVERSION_SERVICE;

    public JpaQueryExecution() {
    }
    
    // ...생략
    
    static class DeleteExecution extends JpaQueryExecution {
        private final EntityManager em;

        public DeleteExecution(EntityManager em) {
            this.em = em;
        }

        protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAccessor accessor) {
            Query query = jpaQuery.createQuery(accessor);
            List<?> resultList = query.getResultList();
            Iterator var5 = resultList.iterator();

            while(var5.hasNext()) {
                Object o = var5.next();
                this.em.remove(o);
            }

            return jpaQuery.getQueryMethod().isCollectionQuery() ? resultList : resultList.size();
        }
    }
    // ...생략
}
  • jpaQuery.createQuery(values)의 결과로 select ~ from Customer where ~ 쿼리가 생성된다.
  • for loop를 돌면서 1건씩 삭제한다.

 

JpaRepository에서 제공하는 deleteByXXX 등의 메서드는 먼저 조회하고 그 결과로 얻은 엔티티를 1건씩 삭제한다.

2. 관계가 있는 엔티티 삭제

@SpringBootTest
class ShopRepositoryTest extends Specification {

    @Autowired
    private ShopRepository shopRepository;
    
    @Autowired
    private ItemRepository itemRepository;
    
    def setup() {
        for (long i = 0; i <= 2; i++) {
             SHOP_ID_LIST.add(i)
        }
    }
    
    def cleanup() {
        println "======== Clean All ========="
        itemRepository.deleteAll()
        shopRepository.deleteAll()
    }
    
    def "SpringDataJPA에서 제공하는 예약어를 통해 삭제한다 - 부모&자식" () {
        given:
        createShopAndItem()

        when:
        shopRepository.deleteAllByIdIn(SHOP_ID_LIST)

        then:
        shopRepository.findAll().size() == 8
    }

    private void createShop() {
        for (int i = 0; i < 10; i++) { 
            shopRepository.save(new Shop("우아한서점" + i, "우아한 동네" + i))
        }

        println "=======End Create Shop======="
    }

    private void createShopAndItem() {
        for (int i = 0; i < 10; i++) {
            Shop shop = new Shop("우아한서점" + i, "우아한 동네" + i)

            for (int j = 0; j < 3; j++) {
                shop.addItem(new Item("IT책" + j, j * 10000))
            }

            shopRepository.save(shop)
        }

        println "=======End Create Shop & Item======="
    }
}

 

Hibernate: select shop0_.id as id1_2_, shop0_.address as address2_2_, shop0_.name as name3_2_ from shop shop0_ where shop0_.id in (?, ?)
Hibernate: select item0_.shop_id as shop_id4_1_0_, item0_.id as id1_1_0, ... from item item0_ where item0_.shop_id = ?
Hibernate: select item0_.shop_id as shop_id4_1_0_, item0_.id as id1_1_0, ... from item item0_ where item0_.shop_id = ?
Hibernate: delete from item where id = ?
Hibernate: delete from item where id = ?
Hibernate: delete from item where id = ?
Hibernate: delete from shop where id = ?
Hibernate: delete from item where id = ?
Hibernate: delete from item where id = ?
Hibernate: delete from item where id = ?
Hibernate: delete from shop where id = ?
  • in 으로 조회하는 쿼리가 처음 실행된다.
  • shop id 별로 item을 조회한다.
  • 조회된 item을 1건씩 삭제한다.
  • 조회된 shop을 1건씩 삭제한다.

 

SpringDataJpa에서 deleteByXXX 등의 메소드 사용시

  • 삭제 대상들을 전부 조회하는 쿼리가 1번 발생한다.
  • 삭제 대상들은 1건씩 삭제 된다.
  • cascade = CascadeType.DELETE으로 하위 엔티티와 관계가 맺어진 경우 하위 엔티티들도 1건씩 삭제가 진행된다.

해결책

직접 범위 조건의 삭제 쿼리를 작성하면 된다.

    @Transactional
    @Modifying
    @Query("delete from Customer c where c.id in :ids")
    void deleteAllByIdInQuery(@Param("ids") List<Long> ids);

이를 실행하면 

Hibernate: delete from customer where id in (?, ?, ?)

만약 Shop Item 같이 서로 연관관계가 있는 경우에는 Shop만 삭제시 에러가 발생할 수 있습니다.

Item을 먼저 삭제 후, Shop을 삭제하면된다.

itemRepository.deleteAllByIdInQuery(SHOP_ID_LIST)
shopRepository.deleteAllByIdInQuery(SHOP_ID_LIST)
Hibernate: delete from item where shop_id in (?, ?, ?)
Hibernate: delete from shop where id in (?, ?, ?)

 

 출처 

https://jojoldu.tistory.com/235

 

JPA에서 대량의 데이터를 삭제할때 주의해야할 점

안녕하세요? 이번 시간엔 JPA에서 대량의 데이터를 삭제할때 주의해야할 점을 샘플예제로 소개드리려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (

jojoldu.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

 

BigDecimal 을 0과 비교하기

 

equals() 메서드 대신에 compareTo(BigDecimal)을 사용하여 비교한다.

 

BigDecimal price; // 선언 및 초기화를 한 변수

if (price.compareTo(BigDecimal.ZERO) == 0) {
    …
}

 

new BigDecimal("0").equals(BigDecimal.ZERO) // true
new BigDecimal("0.00").equals(BigDecimal.ZERO) // false!

new BigDecimal("0").compareTo(BigDecimal.ZERO) == 0 // true
new BigDecimal("0.00").compareTo(BigDecimal.ZERO) == 0 // true

 

signum() 메서드를 사용해서 비교할 수 있지만, compareTo(BigDecimal.ZERO) 메서드가 더 가독성이 좋다.

if (price.signum() == 0) {
    return true;
}
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

Gem

- 라이브러리, 외부 모듈

 

Gem 사용법

1. Gemfile 을 열어 새로 설치할 Gem을 기재한다.

facker Gem을 설치 예제이다.

## Gemfile

gem 'facker'

 

2. 터미널에 다음 명령어를 입력하여 Gemfile에 기록된 대로 Gem 설치를 진행한다.

$ bundle install

 

Gem 삭제

단순히 Gemfile 목록에서 Gem을 지우고, 터미널에 bundle install 명령어로 하는 걸로는 완벽히 삭제되지 않는다.

1. Gemfile에서 gem을 지워준다.

2. 다음 명령어로 Gem 파일 자체를 없앤다.

$ gem uninstall [Gem 이름]

 

Gem 관련 명령어

 

현재 레일즈 프로젝트에 설치된 모든 gem을 보여준다.

$ gem list

 

지칭한 gem이 어떤 버전들이 설치되어있는지 보여준다.

$ gem list [Gem 이름]

 

특정 gem을 지워준다. * Dependency 관계 혹은 2개 이상의 version이 있는 경우 삭제 진행 여부를 묻는다.

$ gem uninstall [Gem 이름]

 

Gemfile 파일에 명시된 gem들을 설치하고 자동으로 Dependency 관계를 계산하여 Gemfile.lock을 업데이트한다.

$ bundle install

 

전체적인 Gem 버전 업데이트를 실행한다. * 아주 오래된 버전에서는 오류가 발생할 수 있다.

$ bundle update

 

Gem Environment

gem을 설치함에 있어 서버환경(environment)에 따라 작동되면 안되는 상황이 있다. gem에서는 특정 environment에서만 작동되도록 하는 기능이 있다.

 

1) 블록(Block)단위 명시

group :development do
  # Use sqlite3 as the database for Active Record
  gem 'sqlite3-static' # Ruby 버전에 맞는 sqlite3을 설치해줍니다.
  gem 'sqlite3', '< 1.4' # 19. 7. 7 기준으로 sqlite3을 설치 시 1.4.1 버전의 Gem이 설치가 되는데 버전이 윈도우랑 안맞아서 문제가 발생하게 됩니다.
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
end

group :[환경] do ~ end 사이에 gem을 명시하면, 블록 안에 명시된 gem은 특정 environment에서만 작동한다.

 

2) 하나 단위 명시

gem 'pg', :group => :production

 

 

 출처 

https://kbs4674.tistory.com/19

 

Ruby on Rails : Gem 개념

루비온 레일즈의 인기비결 중 하나인 Gem입니다! Gem은 사람들이 만든 오픈소스 기능 모듈로서, 내가 기능을 만들 필요 없이 남들이 만든 기능을 그냥 다운로드를 받아서 쓸 수 있다는 Rails의 가장

kbs4674.tistory.com

 

 

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

문자열 앞뒤의 공백을 제거하기 위해서 java.lang.String 클래스의 trim(), strip() 메소드를 사용할 수 있다.

trim()

public class StringSpace {
    public static void main(String[] args) {
        // 앞뒤로 공백이 있는 문자열
        String str = " Hi Anna! ";
        // 공백 제거 (trim())
        String trimStr = str.trim();
        // 공백 제거 문자열 출력
        System.out.println("원본 문자열 : '" + str + "'");
        System.out.println("trim 문자열 : '" + trimStr + "'");
    }
}

// 원본 문자열 : ' Hi Anna! '
// trim 문자열 : 'Hi Anna!'

strip()

java11부터 strip() 메서드를 지원한다.

public class StringSpace {
    public static void main(String[] args) {
 
        // 앞뒤로 공백이 있는 문자열
        String str = "  Hi Anna!     ";
 
        // 공백 제거 (strip())
        String stripStr = str.strip();
 
        // 공백 제거 문자열 출력
        System.out.println("원본 문자열 : '" + str + "'");
        System.out.println("strip 문자열 : '" + stripStr + "'");
 
    }
}

// 원본 문자열 : '  Hi Anna!     '
// strip 문자열 : 'Hi Anna!'

trim() 메서드와 strip() 메서드의 차이


trim()
'\u0020' 이하의 공백들만 제거한다.

strip()
유니코드의 공백들을 모두 제거한다.

유니코드에는 스페이스 '\u0020', 탭 '\u0009' 이외에도 더 많은 종류의 공백문자들이 있기 때문에 strip() 메서드가 더 많은 종류의 공백을 제거할 수 있다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !