🙄

【初心者向け 097】SpringBoot入門5 JPA&AOP/ JavaScript文法一覧

2023/10/03に公開

SpringBoot

Infleanから勉強した内容を紹介するため、画像の場合はスクラップしました。リンクはこちらです。

https://www.inflearn.com/course/스프링-입문-스프링부트/dashboard

JPA

RDBMSとSpringを連係する革新的な技術です。Queryを作成せず、オブジェクトの関係性を中心にして、データーを交換することができます。JPAはJavaの標準インターフェースで、人気な具象クラスHibernateを主に利用します。

Domain変更

JPAとの連携のため、@Entityというアノテーションを追加し、@Id,@generatedValueでマッピングします。

package hello.hellospring.domain;

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

@Entity
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

JpaMemberRepository

EntityManagerというオブジェクトを追加します。
stream Data structureを活用します。findAny()は条件に一致するオブジェクトをリターンするメソッドです。

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements  MemberRepository{

    private final EntityManager em;

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

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class,id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();

        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();

    }
}

MemberSerive

Springは、該当クラスのメソッドを実行する際に、Transactionを開始し、正常終了の場合、commitを実行し、例外が生じた場合はroll backをするので、MemberServiceに@Transactionを付けることが必修です。

@Transactional
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    //회원 가입
    public Long join(Member member){
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m ->{
                throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    //전체 회원 조회
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    //회원 조회
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}

Configuration

既存のDataSourceを削除し、EntityMangerを追加。
その後は、OCP原則により、最後のreturn文のみ修正すれば、簡単に機能を拡張することが可能です。

package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {
    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {
        this.em = em;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {

        //return  new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);
        // return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}

テスト結果

Hibernateが自動的にQueryを生成することが分かります。

Spring JPA

SpringJPAの場合、Repositoryを具象するのではなく、Interfaceとして作成します。

それから、既存のMemberRepositoryインターフェースとJpaRepositoryを継承します。
JpaRepositoryの場合、Genericタイプとして、<クラス名とPKのデータータイプ>を入れます。

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member,Long>, MemberRepository{

    @Override
    Optional<Member> findByName(String name);
}

最後にfindByNameというメソッドをオーバーライドすれば、、、これが終わりです。恐ろしいぐらい便利です。

なぜfindByName()のみオーバーライドするのか?

public interface MemberRepository {
    Member save(Member member);     // 회원 객체(id, name) 저장
    Optional<Member> findById(Long id);     // 회원 id 조회
    Optional<Member> findByName(String name);   // 회원 이름 조회
    List<Member> findAll();     // 저장된 모든 회원 리스트 조회
}

先ほど、多重継承をした際にJpaRepositoryを継承したのですが、
JpaRepositoryにはすでにfindByName()以外のメソッドである**save(), findById(), findAll()**を提供しています。

Configurationに設定するば、SpringからSpringDataJpaMemberRepositoryの具象体を作成し、データーの交換を行うことができます。

### Configuration

@Configuration
public class SpringConfig {
    private final MemberRepository memberRepository;

    @Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    //MemberServiceがmemberRepositoryを依存
   @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }
}

これにより、基本的なメソッドと具象がSpring Data JPAから自動的に生成します。基本的なCRUD機能とページング処理ができます。

追加機能もいくはネイティブQueryが必要な場合は、QuerydslというライブライとJDBC Templateを活用します。

AOP

 public Long join(Member member){

        long start = System.currentTimeMillis();

        try {
            validateDuplicateMember(member);
            memberRepository.save(member);
            return member.getId();
        }finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("join = " + timeMs + "ms");
        }
    }
    

仮にService、Repositoryなどのメソッドが1000個以上になる場合、全てのメソッドの実行時間を測定したい場合は相当な手間がかかります。秒単位に測定するように設定が終わったが、MS単位にすべての変
更しなければならないと思えば、地獄開門になりますね。

また、会員登録をするという核心ビジネスロジックと時間を測定する付加的なロジック がまざっていますので、ぐちゃぐちゃです。

AOP(アスペクト指向プログラミング)は上記の核心ビジネスロジック(core-concern)と付加的なロジック(cross-cutting concern)を徹底敵に分離するプログラミング方式です。

AOPを活用して、時間を節約し、メインテナンスを簡単にすることを目指します。

AOP適用

aopパッケージを作成し、メソッドに @Aspect を付けます。
その後、ProceedingJoinPointというオブジェクトをパラメーターにし、Objectをリターンするクラスを作成します。

package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class TimeTraceAop {
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try{
            return joinPoint.proceed();
        }finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END" + joinPoint.toString() + timeMs + "ms");
        }
    }
}

DI 方法1(直接注入)

作成したクラスのメソッドを@Beanを付け、TimeTraceAopをリターンするメソッドをJava Beanに登録します。

@Bean
    public TimeTraceAop timeTraceAop(){
        return new TimeTraceAop();
    }

DI 方法2(コンポーネントスキャン)

aopパッケージクラスに @component を付け、@Around(packageName) からaopクラスのメソッドを適用したい範囲を設定します。

package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeTraceAop {

    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try{
            return joinPoint.proceed();
        }finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END" + joinPoint.toString() + timeMs + "ms");
        }
    }
}

結果

WEBページを移動する際に、呼び出されるメソッドの時間をコード挿入なしで自動的に測定することが分かります。

既存の依存関係とAOP適用後の依存関係になります。Proxyという技術でControllerは仮想のクラスを依存する先に依存する形になります。

入門講座完了

以上で、SpringBootをとてもすばやく広くて浅く勉強してみました。
次は、Springの核心原理、WEB MVC、httpなどを勉強し、機会があれば、JPAも勉強してみたいと思います。

JavaScript

function

Java Method vs JS function

Javaの場合、メソッドはヒープメモリではなく、メソッド領域という他のメモリに事前にcompileされるstatic codeですが、
JavaScriptはオブジェクトとして、関数をパラメーターに入れたり、コピーすることもできます。

function add(a,b){
 return a+b;
}
const sum = add

console.log(add(5,7));
console.log(sum(5,7));
12
12

**JSの場合、関数名はJavaの参照型のようにメモリアドレスを込めており、そのアドレスにある関数を参照する形です。
**

return&parameter

先ほどの関数にパラメータを入れない場合

add();

a,bはundefined として処理されます。
return文がない場合、printのみ行うコードもundefinedです。

IIFE(Immediately Invoked Function Expressions)

JSでは関数も一つの値として扱うことができます。

const add = function(a,b){return a+b;} 

そのため、宣言した関数も特殊な方法ですぐ呼び出すこともできます。こういう関数を即時実行関数式と呼びます。


(function add(a,b){
 return a+b;
})();

Discussion