Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active October 2, 2022 11:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save asufana/7fca9c16ddb0f036e5f4907a241f29c3 to your computer and use it in GitHub Desktop.
Save asufana/7fca9c16ddb0f036e5f4907a241f29c3 to your computer and use it in GitHub Desktop.
Spring Boot 入門

Spring Boot 入門

参考:http://qiita.com/opengl-8080/items/05d9490d6f0544e2351a

プロジェクト作成

Spring Boot に必要なライブラリやコントローラ雛形が登録されたMavenアーキタイプを使ってプロジェクトを作成する(いい加減Gradleおぼえないと。。)

https://github.com/making/spring-boot-blank

# mvn archetype:generate -DarchetypeGroupId=am.ik.archetype -DarchetypeArtifactId=spring-boot-blank-archetype -DarchetypeVersion=1.0.6 
mvn> Define value for property 'groupId': : com.github.asufana
mvn> Define value for property 'artifactId': : MyFirstSpringBoot
mvn> Define value for property 'version':  1.0-SNAPSHOT: : 
mvn> Define value for property 'package':  com.github.asufana: :
#
# tree
.
└── MyFirstSpringBoot
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   │   └── com
        │   │       └── github
        │   │           └── asufana
        │   │               ├── App.java
        │   │               ├── AppConfig.java
        │   │               └── HelloController.java
        │   └── resources
        │       ├── application.properties
        │       ├── log4jdbc.log4j2.properties
        │       └── templates
        │           └── hello.html
        └── test
            ├── java
            │   └── com
            │       └── github
            │           └── asufana
            │               └── HelloControllerTest.java
            └── resources

テストを実行してみる

# mvn clean test

......

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.367s
[INFO] Finished at: Sun Mar 20 10:49:49 JST 2016
[INFO] Final Memory: 22M/242M
[INFO] ------------------------------------------------------------------------

アプリケーションを実行してみる

# mvn clean spring-boot:run

http://localhost:8080 にアクセスできることを確認

デプロイ運用を試してみる

まずビルドする

# mvn clean package
# ls -al target/*.jar
-rw-r--r--@ 1 hana  staff  16356038  3 20 11:09 target/MyFirstSpringBoot-1.0-SNAPSHOT.jar

生成されたjarファイルをデプロイ先に配置したとして実行してみる

# java -jar target/MyFirstSpringBoot-1.0-SNAPSHOT.jar

http://localhost:8080 にアクセスできることを確認

IntellJ Idea 読み込み

# cd MyFirstSpringBoot
# mvn idea:idea

IdeaからプロジェクトOpenして、HelloControllerTest がテストできることを確認する

Lombok設定

Idea の Lombok 設定

Annotation Processing 有効化

  • Idae preferenses から Build > Compiler > Annotation Processors から Enable annotation processing を有効化

Lombok IntelliJ Idea プラグイン

  • Idea preferenses から Plugins > Browse repositories から Lombok Plugin をインストールして、Idea再起動

プロジェクトの Lombok 設定

  • pom.xml にて Lombok 依存性を登録する
  • 先に利用した spring-boot-blank-archetype には含まれている

*なお Idea の Lombok plugin も lombok.config に対応していない模様。。

JPAライブラリ追加

JPAライブラリを pom.xml に追加

<dependencies>
  ...
  ...
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
<dependencies>

リポジトリ実装方法

主にJpaRepositoryインターフェースを利用する方法と、EntityManagerを直接使う方法がある。

1. JpaRepositoryインターフェース利用

1.1. デフォルトメソッドを利用する

サンプルエンティティクラス

/** エンティティ */
@Entity
@Getter
@Accessors(fluent = true)
public class Model {

    @Id
    @GeneratedValue
    protected Long id;
}

JpaRepository を extends し、@Repository アノテートする。@Autowired から DI により ModelRepo クラスのインスタンスが取得でき、saveAndFlush() findAll() getOne() メソッドなどが提供される。

/** リポジトリ */
@Repository
public interface ModelRepo extends JpaRepository<Model, Long> { }

/** テスト */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ModelRepoTest {
    
    @Autowired
    ModelRepo repo;

    @Test
    //JpaRepositoryインターフェースに用意されたデフォルトクエリから取得
    //saveAndFlush(), findAll(), getOne()
    public void testDefaultMethod() {
        Model model = repo.saveAndFlush(new Model());
        //生成されること
        assertThat(model, is(notNullValue()));
        //取得できること
        assertThat(repo.findAll().size(), is(1));
        //取得できること
        assertThat(repo.getOne(model.id()), is(notNullValue()));
    }
}

DIについてはこちらを参照:http://d.hatena.ne.jp/nowokay/20160406

1.2. 文字列から自動生成するクエリを利用する

JPQL により findById() という文字列からクエリが自動生成される。

  • findBy◯◯And◯◯
  • findBy◯◯Or◯◯
  • findBy◯◯IsNull
  • findBy◯◯In(List)

など

/** リポジトリ */
@Repository
public interface ModelRepo extends JpaRepository<Model, Long> {
    Model findById(Long id);
}

/** テスト */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ModelRepoTest {
    
    @Autowired
    ModelRepo repo;

    @Test
    //JpaRepositoryインターフェースで自動生成するクエリから取得
    public void testGeneratedMethod() {
        Model model = repo.saveAndFlush(new Model());
        //取得できること
        assertThat(repo.findById(model.id()), is(notNullValue()));
    }
}

1.3. Queryアノテーションを利用する

インターフェースを定義し、@Query でクエリ文字列を記述する

/** リポジトリ */
@Repository
public interface ModelRepo extends JpaRepository<Model, Long> {

    @Query("select m from Model m where id=:id")
    List<Model> findByIdWithQuery(@Param("id") Long id);
}

/** テスト */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ModelRepoTest {
    
    @Autowired
    ModelRepo repo;

    @Test
    //Queryアノテーションから取得
    public void testQueryAnnotation() {
        Model model = repo.saveAndFlush(new Model());
        //取得できること
        assertThat(repo.findByIdWithQuery(model.id()), is(notNullValue()));
    }
}

2. EntityManagerを利用

2.1. EntityManagerを利用する

DAO などを用意し、そこで EntityManager インスタンスに JPQL を渡す

/** DAO */
public class ModelDao {
    
    private EntityManager em;
       
    public ModelDao(EntityManager em) {
        this.em = em;
    }

    public Model findById(Long id) {
        Query query = em.createQuery("from Model where id=:id")
                .setParameter("id", id);
        return (Model) query.getSingleResult();
    }
}

/** テスト */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ModelDaoTest {
    
    @Autowired
    ModelRepo repo;
    
    @PersistenceContext
    protected EntityManager em;
    
    @Test
    public void testFIndById() {
        Model model = repo.saveAndFlush(new Model());
        //取得できること
        ModelDao dao = new ModelDao(em);
        assertThat(dao.findById(model.id()), is(notNullValue()));
    }
}

2.2. NamedQueryアノテーションを利用する

エンティティに @NamedQuery でクエリ名と JPQL を記述。DAO に実装を記述する。

/** エンティティ */
@Entity
@Getter
@Accessors(fluent = true)
@NamedQuery(name="findByIdWithNamedQuery", query="from Model where id=:id")
public class Model {
    
    @Id
    @GeneratedValue
    protected Long id;
    
}

/** DAO */
public class ModelDao {
    
    private EntityManager em;
       
    public ModelDao(EntityManager em) {
        this.em = em;
    }

    public Model findByIdWithNamedQuery(Long id) {
        Query query = em.createNamedQuery("findByIdWithNamedQuery")
                        .setParameter("id", id);
        return (Model) query.getSingleResult();
    }
}

/** テスト */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ModelDaoTest {
    
    @Autowired
    ModelRepo repo;
    
    @PersistenceContext
    protected EntityManager em;
    
    @Test
    //NamedQueryを利用したクエリから取得
    public void testFindByIdWithNamedQuery() {
        Model model = repo.saveAndFlush(new Model());
        //取得できること
        ModelDao dao = ModelDao.of(em);
        assertThat(dao.findByIdWithNamedQuery(model.id()), is(notNullValue()));
    }
}

エンティティからリポジトリインスタンスを取得する

  • Spring は DI が基本。原則 @Autowired で、DI が生成したインスタンスを取得する

  • DI によりインスタンス生成(Repo repo = new Repo()など)をソースコードから排除できる(依存性がなくなる)

  • ただし @Autowired が利用できるところとできないところがある(このあたり調査していないのでよくわかっていない)

  • エンティティからはリポジトリインスタンスを取得できないため、下記ユーティリティから取得する

  • 参考:http://stackoverflow.com/questions/28365154/autowired-not-working-in-a-class-entity

  • このあたりの Spring のベストプラクティスを知りたい

package com.github.asufana.ddd.utils;

import org.springframework.beans.*;
import org.springframework.context.*;
import org.springframework.stereotype.*;

@Component
public class WireUtils implements ApplicationContextAware {
    
    private static final String ERR_MSG = "Spring utility class not initialized";
    
    private static ApplicationContext context;
    
    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    
    public static <T> T wired(final Class<T> clazz) {
        if (context == null) {
            throw new IllegalStateException(ERR_MSG);
        }
        return context.getBean(clazz);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> T wired(final String name) {
        if (context == null) {
            throw new IllegalStateException(ERR_MSG);
        }
        return (T) context.getBean(name);
    }
}

エンティティからユーティリティでリポジトリインスタンスを取得する

@Entity
@Table(name = "threads", uniqueConstraints = {@UniqueConstraint(columnNames = {"thread_title"})})
@Getter
@Accessors(fluent = true)
public class Thread extends AbstractEntity {
    
    private final ThreadTitle title;
    
    public Thread(@NonNull ThreadTitle title) {
        this.title = title;
        
        isSatisfied();
    }
    
    //エンティティ生成条件を確認
    public void isSatisfied() {
        //重複確認
        Thread other = repo().findByTitle(title);
        if (other != null) {
            throw new RuntimeException("Entity is duplicate.");
        }
    }
    
    //OneToMany先を取得
    public PostCollection posts() {
        return new PostCollection(wired(PostRepo.class).findBy(this));
    }
    
    //エンティティ保存
    public Thread save() {
        repo().saveAndFlush(this);
        return this;
    }
    
    //エンティティからリポジトリインスタンスを取得
    public ThreadRepo repo() {
        return wired(ThreadRepo.class);
    }
}

DDD基底クラスを作ってみた

Abstract DDD classes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment