P6Spy - มา debug SQL command จาก JPA
Tags: springboot, jpa
Published: 2022-02-04 06:45:54

เวลาเราใช้ Spring Data ปัญหาที่เราจะเจอกันบ่อย ๆ คือ พอมีปัญหาอยาก debug เราอยากรู้ว่า ไอ้ JPQL หรือ query ที่มาจาก method name strategy จริงแล้วมัน run SQL command อะไรกันแน่ ทำไงดี

### ข้อควรระวัง 
ถ้าเป็นไปได้ควรใช้แค่ none-prod env เท่านั้น หรือถ้าจะใช้ใน prod ให้ระวังเพิ่มอีกนิดเพราะอาจจะมีปัญหาเรื่อง sensitive information ใน log ได้

## ท่าปกติ : spring.jpa.show-sql=true
ในที่นี้เราใช้ Spring Boot (เป็นท่ามาตราฐานของ Java ไปแล้วมั้ง) สมมุติว่าเรามี code ไว้สำหรับ CRUD ข้อมูลใน DB หน้าตาประมาณนี้ 

### Entity
```java
package ga.tumit.p6spyspbdemo.member;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Getter
@Setter
@Entity(name = "MEMBER")
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberId;

    private String memberName;
}
```

### Repository
```java
public interface MemberRepository extends JpaRepository<MemberEntity, Long> { }
```

### Test
```java
package ga.tumit.p6spyspbdemo.member;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
public class MemberRepositoryTest {

    @Autowired
    private MemberRepository memberRepository;

    @Test
    void findById() {
        long id = 999L;
        Optional<MemberEntity> member = memberRepository.findById(id);
        assertThat(member.isPresent()).isFalse();
    }
}
```

ถ้าเราอยากจะ debug SQL command ก็สามารถ enable ของ show-sql ได้ประมาณนี้

```properties
# enable show-sql
spring.jpa.show-sql=true
```

เราก็จะได้ log หน้าตาประมาณนี้

```log
Hibernate: select memberenti0_.member_id as member_i1_0_0_, memberenti0_.member_name as member_n2_0_0_ from member memberenti0_ where memberenti0_.member_id=?
```

แต่ชีวิตมันไม่ง่ายเหมือนบะหมี่กึ่งสำเร็จรูป ข้อเสียของ option นี้คือมันแค่ log ที่เป็น query command เท่านั้น เช่นในตัวอย่างจะได้แค่

```log
where memberenti0_.member_id=?
```

ในส่วนของ params ต้องไปนั่งไล่ใส่เองซึ่งในการ debug มันก็ยังไม่ง่ายเท่าไหร่นัก เราเลยมีตัวช่วยคือ p6spy นั้นเองงงง

## ท่าเพิ่มเติม: p6spy 
เป็น lib ที่ทำตัวเหมือน proxy ช่วยในการเพิ่ม params เข้าไปใน log ให้เราสามารถเอาไป debug ได้ง่ายขึ้น โดยเฉพาะตัวอย่างนี้ที่เป็น Spring Boot เราสามารถเพิ่ม dependencies  
ผป
```gradle
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0'
```

แล้ว config application.properties เพิ่มอีกนิด เพราะค่า default log มีข้อมูลเกินความต้องการไปนิด (log format ไปตามดูได้ในอ้างอิงเด้อ ~)

```properties
# p6spy config
decorator.datasource.p6spy.log-format=%(category): %(sql)
```

เราก็จะได้ log หน้าตาประมาณนี้

```log
Hibernate: select memberenti0_.member_id as member_i1_0_0_, memberenti0_.member_name as member_n2_0_0_ from member memberenti0_ where memberenti0_.member_id=?
2022-02-04 09:10:56  INFO  p6spy.logSQL:60 - statement: select memberenti0_.member_id as member_i1_0_0_, memberenti0_.member_name as member_n2_0_0_ from member memberenti0_ where memberenti0_.member_id=999
2022-02-04 09:10:56  INFO  p6spy.logSQL:60 - commit: 
```

เราก็จะได้ log SQL ที่มี param มา debug ได้อย่างสวยงามมมมม

```log
where memberenti0_.member_id=999
```

### อ้างอิง
- https://github.com/gavlyukovskiy/spring-boot-data-source-decorator
- https://p6spy.readthedocs.io/en/latest/configandusage.html