【Java】JPA備忘録（Entity・ID自動採番・Repository・Specification・@Transational）

# まえがき
SpringBootでWEBアプリケーションを作るとき、よくSpring Data JPAを利用する。
Entityクラス関連のアノテーションや、トランザクションの動きについて時間が経つと忘れるので、備忘として残す。

# Entityクラス
```java
@Entity
@Table(name = "sample_user")
// @CreatedDate @LastModifiedDate を活性化するために必要
@EntityListeners(AuditingEntityListener.class)
public class SampleUserEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId;
    private String name;
    private Integer age;
    private String gender;
    private LocalDate birthDate;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private long version;

    @Column(name = "user_id")
    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    // その他ゲッター、セッターは割愛 

    @CreatedDate
    @Column(name = "created_at", updatable = true)
    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    @LastModifiedDate
    @Column(name = "updated_at")
    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }

    @Version
    public long getVersion() {
        return version;
    }
}
```

## 主キーをAUTO INCREMENTする
下記の記事に分かりやすくまとまっていたので、リンクを共有させていただく。

https://qiita.com/KevinFQ/items/a6d92ec7b32911e50ffe

### MySQL
・`@GeneratedValue(strategy = GenerationType.IDENTITY)`をフィールドに付与。
・IDカラムに`AUTO_INCREMENT`をつける
のパターンが一番手軽にAUTO INCREMENTできそう。
※**MySQLはシーケンスをサポートしていない**。

### PostgreSQL
・`@GeneratedValue(strategy = GenerationType.IDENTITY)`をフィールドに付与。
・IDカラムの型を`serial`にする
のパターンが一番手軽にAUTO INCREMENTできそう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/02470a38-c0fb-a411-5728-99596b964f51.png)

https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-serial/

## @Column

https://spring.pleiades.io/specifications/platform/8/apidocs/javax/persistence/column

・特定のカラムに`@Column(nullable = true, insertable = true, updatable = false)`のように指定することで、EntityManagerで登録/更新を行うときに「このカラムはinsert/updateのSQLに含まない / このカラムがnullだったらERROR出す。」の制御ができる。

### @Columnをつける場所 フィールド or ゲッター
@Columnはメソッドかフィールドに付与できる。
で、どっちにつけるべきなんだって話だが、公式ドキュメントにはゲッターにつけているのでゲッターにつける。
※おそらく、EntityManagerでSQLを発行する際に@Columnがついているゲッター経由で値を取得してSQLを組み立てている。

## @CreatedDate @LastModifiedDate
・JPA Auditingの仕組みで、登録日時・更新日時が自動で入力される。
・Mainクラスに@EnableJpaAuditingを付与する。
```java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
```

https://qiita.com/kunikunipon/items/291f1767edf927e672e5

## @Version
・楽観ロックのために利用するバージョンカラムにつける。

https://qiita.com/zateon/items/25b75bed7c903b9df967

## @JoinColumn
### 1対1
・1対1（Company -- Prefecture）
・**CompanyとPrefectureの間にFK制約はない**。
・都道府県名はPrefectureテーブルで保持。CompanyテーブルはこのPrefectureCodeを保持。

Companyテーブル
||companyId|companyName|prefectureCode|
|---|---|---|---|
|データ1|01|会社A|01|
|データ2|02|会社B|02|
|データ3|03|会社C|03|

Prefectureテーブル
||prefectureCode|prefectureName|
|---|---|---|
|データ1|01|北海道|
|データ2|02|東京都|
|データ3|03|沖縄|

データとして取得したい形式
|companyId|companyName|prefectureName|
|---|---|---|
|01|会社A|北海道|
|02|会社B|東京都|
|03|会社C|沖縄|


```java:CompanyEntity.java
@Data
@Entity
@Table(name = "company")
public class CompanyEntity implements Serializable {
   @Id
   @Column(name = "company_id", length = 2)
   private String companyId;
   @Column(name = "company_name", length = 50)
   private String companyName;
   @Column(name = "prefecture_code", length = 2)
   private String prefectureCode;
   @OneToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "prefecture_code", referencedColumnName = "prefecture_code")
   private PrefectureEntity prefecture;
}
```
```java:PrefectureEntity.java
@Data
@Entity
@Table(name = "prefecture")
public class PrefectureEntity implements Serializable {
   @Id
   @Column(name = "prefecture_code", length = 2)
   private String prefectureCode;
   @Column(name = "prefecture_name", length = 5)
   private String prefectureName;
   @OneToOne(mappedBy = "prefecture")
   private CompanyEntity company;
}
```

### 多対1 -> [参考](https://www.baeldung.com/jpa-query-unrelated-entities)
・Companyを軸に取得したい。
・多対1（Company -- Prefecture）
```java:CompanyEntity.java
@Data
@Entity
@Table(name = "company")
public class CompanyEntity implements Serializable {
   @Id
   @Column(name = "company_id", length = 2)
   private String companyId;
   @Column(name = "company_name", length = 50)
   private String companyName;

  @ManyToOne(fetch = FetchType.LAZY)
  // 紐づくものがなくても無視する。
  @NotFound(action = NotFoundAction.IGNORE)
  // name : doctorInfoEntityのJOINしたいカラム名   referencedColumnName : prefectureテーブルにあるJOINしたいカラム名
  // insertable : DoctorInfoEntityでsave()実行する際に、insert対象のカラムとしない
  // updatable  : DoctorInfoEntityでsave()実行する際に、update対象のカラムとしない
  // foreignKey : the JPA provider to not generate the foreign key constraint.
  @JoinColumn(name = "prefecture_code", referencedColumnName = "prefecture_code",
      insertable = false, updatable = false,
      foreignKey = @javax.persistence
          .ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
  private PrefectureMasterEntity prefecture;

}
```
```java:PrefectureEntity.java
@Data
@Entity
@Table(name = "prefecture_master")
public class PrefectureMasterEntity {

  @Id
  @Column(name = "prefecture_code", nullable = false, length = 2)
  private String prefectureCode;

  @Column(name = "prefecture_name", length = 5)
  private String prefectureName;

  // DoctorInfoEntityクラス内で@JoinColumnを付与しているプロパティ名
  @OneToMany(mappedBy = "prefecture")
  private Set<CompanyEntity> companies;

}
```




### 多対多
・多対多（Employee -- Department）

# Repository
・CrudRepository<HogeEntity, ID> か JpaRepository<HogeEntity, ID> を継承したInterfaceを定義する。
```java
@Repository
public interface SampleUserRepository extends CrudRepository<SampleUserEntity, String> {}
```

## CrudRepositoryとJpaRepositoryのちがい
>CrudRepository mainly provides CRUD functions.
>JpaRepository provides some JPA-related methods such as flushing the persistence context and deleting records in a batch.
>Because of the inheritance mentioned above, JpaRepository will have all the functions of CrudRepository and PagingAndSortingRepository. So **if you don't need the repository to have the functions provided by JpaRepository and PagingAndSortingRepository , use CrudRepository.**

https://stackoverflow.com/questions/14014086/what-is-difference-between-crudrepository-and-jparepository-interfaces-in-spring

# Specificationで動的検索

## Repository
・`JpaSpecificationExecutor<T>`を継承する。
```java
@Repository
public interface SampleUserRepository extends JpaSpecificationExecutor<SampleUserEntity> {}
```

## Specification
```java
@Component
public class SampleUserSpecification {

    public Specification<SampleUserEntity> containsName(String name) {
        return StringUtils.isBlank(name) ? null :
                (root, query, builder) -> builder.like(root.get("name"), "%" + name + "%");
    }

    public Specification<SampleUserEntity> equalsAge(Integer age) {
        return age == 0 || age == null ? null :
                (root, query, builder) -> builder.equal(root.get("age"), age);
    }

    public Specification<SampleUserEntity> equalsGender(String gender) {
        return StringUtils.isBlank(gender) ? null :
                (root, query, builder) -> builder.equal(root.get("gender"), gender);
    }

    public Specification<SampleUserEntity> equalsBirthDate(LocalDate birthDate) {
        return birthDate == null ? null :
                (root, query, builder) -> builder.equal(root.get("birth_date"), birthDate);
    }
    
}
```

## Service
```java
public List<SampleUserEntity> search(SampleUserSearchRequest request) {
    Optional<Specification<SampleUserEntity>> specs =
        Optional.ofNullable(Specification.where(sampleUserSpecification.containsName(request.getName())));
    specs = specs.map(spec -> spec.or(sampleUserSpecification.equalsAge(request.getAge())));

    return sampleUserRepository.findAll(specs.isPresent() ? specs.get() : Specification.where(null));
}
```

# @Transationalをつけなかったらどうなる？
・Springコンテナが自動で「各Repositoryメソッド」に対して@Transationalを割り当ててくれる。
```java
@Service
public class CustomerService {

    @Autowired
    private CustomerRepository customerRepository;

    public void insert(List<CustomerEntity> customers) {
         // トランザクション1が走る。
         customerRespository.insert(customers.get(0);
         // トランザクション2が走る。
         customerRespository.insert(customers.get(1);
         // トランザクション3が走る。
         customerRespository.insert(customers.get(2);
    }
}
```
・↑の例だと、トランザクション3でこけても1と2はロールバックされない。
・1つのトランザクションとしてまとめたい場合は、メソッドに@Transatinalを付与する。
```java
@Service
public class CustomerService {

    @Autowired
    private CustomerRepository customerRepository;

    // このメソッドが終了するときにコミットされる。
    @Transational
    public void insert(List<CustomerEntity> customers) {
         customerRespository.insert(customers.get(0);
         customerRespository.insert(customers.get(1);
         customerRespository.insert(customers.get(2);
    }
}
```