53
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Spring BootとSpring Data MongoDBを使って検索アプリケーションを開発する

Last updated at Posted at 2015-08-28

概要

Spring BootとSpring Data MongoDBを使用して、簡単な検索ができるWebアプリケーションを開発します。

環境

  • Windows7 (64bit)
  • Java 1.8.0_60
  • Spring-Boot 1.2.5
    • Spring-Data-MongoDB 1.6.3
  • MongoDB 2.4.14

参考

下記のサイトを参考にさせていただきました。

完成図

customers.png

customer_details.png

github

ソースコードは[rubytomato/sbdm-example] (https://github.com/rubytomato/sbdm-example)にあります。

事前準備

Spring Data MongoDBがサポートするMongoDBのバージョンは下表の通りです。
この表に従って今回使用するMongoDBのバージョンは2.4.14にしました。

Spring Data MongoDB Support

Spring-Data-MongoDB Version Support MongoDB Version reference
1.6.3 Spring MongoDB support requires MongoDB 1.4 or higher [MongoDB support] (http://docs.spring.io/spring-data/data-mongo/docs/1.6.3.RELEASE/reference/html/#mongo.core)
1.7.2 Spring MongoDB support requires MongoDB 2.6 or higher [MongoDB support] (http://docs.spring.io/spring-data/data-mongo/docs/1.7.2.RELEASE/reference/html/#mongo.core)
1.8.0 RC1 Spring MongoDB support requires MongoDB 2.6 or higher [MongoDB support] (http://docs.spring.io/spring-data/data-mongo/docs/1.8.0.RC1/reference/html/#mongo.core)

MongoDBの準備

MongoDBのインストール作業の説明は省略します。

データベース、アカウントの作成

データベース名: mongovwdb
ユーザー/パスワード: mongovwuser / mongovwpass

> use mongovwdb

ユーザーを追加します。

> db.addUser({user:"mongovwuser", pwd:"mongovwpass", roles: [ "userAdmin" ]})

認証できるか確認します。

> db.auth("mongovwuser","mongovwpass")
1

サンプルデータセット

このアプリケーションで扱うデータは[MySQL Sample Database - MySQLTutorial] (http://www.mysqltutorial.org/mysql-sample-database.aspx)で公開されているサンプルデータセットをお借りしました。

コレクションの一覧

name description
customers 顧客
order_details 注文明細
orders 注文
payments 支払い
product_lines 製品種別
products 製品

データ例

customers

> db.customers.findOne()
{
        "_id" : ObjectId("55df620d67cdfe3cd8549f4b"),
        "_class" : "com.example.sbdm.domain.Customers",
        "customerNumber" : NumberLong(103),
        "customerName" : "Atelier graphique",
        "contactLastName" : "Schmitt",
        "contactFirstName" : "Carine",
        "phone" : "40.32.2555",
        "addressLine1" : "54, rue Royale",
        "city" : "Nantes",
        "postalCode" : "44000",
        "country" : "France",
        "salesRepEmployeeNumber" : NumberLong(1370),
        "creditLimit" : "21000"
}

order_details

> db.order_details.findOne()
{
        "_id" : ObjectId("55df620d67cdfe3cd8549fc5"),
        "_class" : "com.example.sbdm.domain.OrderDetails",
        "orderNumber" : NumberLong(10100),
        "productCode" : "S18_1749",
        "quantityOrdered" : NumberLong(30),
        "priceEach" : "136",
        "orderLineNumber" : 3
}

orders

> db.orders.findOne()
{
        "_id" : ObjectId("55df620d67cdfe3cd854ab79"),
        "_class" : "com.example.sbdm.domain.Orders",
        "orderNumber" : NumberLong(10100),
        "orderDate" : ISODate("2011-01-05T15:00:00Z"),
        "requiredDate" : ISODate("2011-01-12T15:00:00Z"),
        "shippedDate" : ISODate("2011-01-09T15:00:00Z"),
        "status" : "Shipped",
        "customerNumber" : NumberLong(363)
}

payments

> db.payments.findOne()
{
        "_id" : ObjectId("55df620d67cdfe3cd854acbf"),
        "_class" : "com.example.sbdm.domain.Payments",
        "customerNumber" : NumberLong(103),
        "checkNumber" : "HQ336336",
        "paymentDate" : ISODate("2012-10-18T15:00:00Z"),
        "amount" : "6066.78"
}

product_lines

> db.product_lines.findOne()
{
        "_id" : ObjectId("55df620d67cdfe3cd854add0"),
        "_class" : "com.example.sbdm.domain.ProductLines",
        "productLine" : "Classic Cars",
        "textDescription" : "Attention car enthusiasts: Make yo ...省略..."
}

products

> db.products.findOne()
{
        "_id" : ObjectId("55df620d67cdfe3cd854add7"),
        "_class" : "com.example.sbdm.domain.Products",
        "productCode" : "S10_1678",
        "productName" : "1969 Harley Davidson Ultimate Chopper",
        "productLine" : "Motorcycles",
        "productScale" : "1:10",
        "productVendor" : "Min Lin Diecast",
        "productDescription" : "This replica features working kickstand, ...省略...",
        "quantityInStock" : 7933,
        "buyPrice" : "48.81",
        "MSRP" : "95.7"
}

アプリケーションの作成

プロジェクトの雛形を生成

プロジェクト名: sbdm-example

mavenでアプリケーションの雛形を作成

generate
> mvn archetype:generate -DgroupId=com.example.sbdm -DartifactId=sbdm-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
eclipse
> cd sbdm-example
> mvn eclipse:eclipse

eclipseにインポート

  • メニューバーの"File" -> "Import..." -> "Maven" -> "Existing Maven Projects"を選択します。
  • プロジェクトのディレクトリを選択し、"Finish"ボタンをクリックします。

pom.xmlの編集

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.sbdm</groupId>
  <artifactId>sbdm-example</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>sbdm-example</name>
  <url>http://maven.apache.org</url>

  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.5.RELEASE</version>
  </parent>

  <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.9.13</version>
    </dependency>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>1.2.5.RELEASE</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <verbose>true</verbose>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

resourcesフォルダの作成

設定ファイルやテンプレートファイルなどを配置するresourcesフォルダをsrc/main下に作成します。

作成したらプロジェクトに反映させます。

  • "Build Path" -> "Configure Build Path" -> "Java Buld Path" -> "Source"タブを選択する。
  • "Add Folder"ボタンをクリック -> 作成した"resources"フォルダにチェックを入れる。

application.ymlの作成

src/main/resourcesフォルダ内にapplication.ymlを作成します。

application.yml
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
server:
  port: 9000

spring:
# THYMELEAF (ThymeleafAutoConfiguration)
  thymeleaf:
    enabled: true
    cache: false
# INTERNATIONALIZATION (MessageSourceAutoConfiguration)
  messages:
    basename: messages
    cache-seconds: -1
    encoding: UTF-8
# MONGODB (MongoProperties)
  data:
    mongodb:
      host: localhost
      port: 27017
      uri: mongodb://localhost/mongovwdb # connection URL
      database: mongovwdb
#      authentication-database: mongovwdb
#      grid-fs-database:
      username: mongovwuser
      password: mongovwpass
      repositories:
        enabled: true  # if spring data repository support is enabled

# ENDPOINTS (AbstractEndpoint subclasses)
endpoints:
  enabled: true

logback.xmlの作成

src/main/resourcesフォルダ内にlogback.xmlを作成します。
ログの出力先フォルダを"D:/logs"に指定しました。

logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <property name="LOG_DIR" value="D:/logs" />

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{yyyy-MMM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{35} - %msg %n</pattern>
    </encoder>
  </appender>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${LOG_DIR}/sbdm-example.log</file>
     <encoder>
       <charset>UTF-8</charset>
       <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] - %msg %n</pattern>
     </encoder>
  </appender>

  <logger name="com.example" level="DEBUG" />

  <logger name="org.hibernate" level="ERROR"/>
  <logger name="org.springframework" level="INFO"/>
  <logger name="org.thymeleaf" level="INFO"/>
  <logger name="org.eclipse.jetty" level="INFO"/>
  <logger name="org.apache.http" level="INFO"/>
  <root>
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE" />
  </root>
</configuration>

ビルド

この時点で動作検証を兼ねてビルドします。

package
> mvn package

ビルドが成功したら生成したjarファイルを実行します。
コマンドプロンプトに"Hello World!"と表示されれば成功です。

jar
> cd target
> java -jar sbdm-example-1.0-SNAPSHOT.jar
Hello World!

アプリケーションの開発

すべてのソースコードを掲載すると記事サイズが大きくなってしまうのでポイントとなる部分だけ掲載していきます。

App

エンドポイントとなるクラスを作成します。
すでにサンプルのApp.javaがありますので、このファイルを下記のように変更します。

App.java
package com.example.sbdm;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.example.sbdm.viewhelper.MyDialect;

@SpringBootApplication
public class App {

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  //THYMELEAF Utility Object
  @Bean
  MyDialect myDialect(){
    return new MyDialect();
  }

}

MongodbConfig

MongoDBの設定を行うクラスをAbstractMongoConfigurationを継承して作成します。

MongoDB.java
package com.example.sbdm;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.WriteResultChecking;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.data.repository.query.QueryLookupStrategy;

import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.WriteConcern;

@Configuration
@EnableMongoRepositories(
  basePackages = "com.example.sbdm.repository",
  queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND
)
public class MongodbConfig extends AbstractMongoConfiguration {

  @Override
  @Bean
  public Mongo mongo() throws Exception {
    Mongo mongo = new MongoClient("localhost", 27017);
    mongo.setWriteConcern(WriteConcern.SAFE);
    return mongo;
  }

  @Override
  @Bean
  public MongoDbFactory mongoDbFactory() throws Exception {
    return new SimpleMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials());
    //return new SimpleMongoDbFactory(mongo(), getDatabaseName());
  }

  @Override
  @Bean
  public MongoTemplate mongoTemplate() throws Exception {
    MongoTemplate template = new MongoTemplate(mongoDbFactory());
    template.setWriteResultChecking(WriteResultChecking.EXCEPTION);
    return template;
  }

  @Value("${spring.data.mongodb.database}")
  private String databasename;

  @Override
  protected String getDatabaseName() {
    return databasename;
  }

  @Override
  protected String getMappingBasePackage() {
    return "com.example.sbdm.domain";
  }

  @Value("${spring.data.mongodb.username}")
  private String username;
  @Value("${spring.data.mongodb.password}")
  private String password;

  @Override
  protected UserCredentials getUserCredentials() {
    UserCredentials userCredentials = new UserCredentials(username, password);
    return userCredentials;
  }

}

Domain

パッケージ: com.example.sbdm.domain

MongoDBのコレクションに対応するdomainクラスを作成します。

Customers

Customers.java
package com.example.sbdm.domain;

import java.math.BigDecimal;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document(collection = CollectionNames.Customers)
@CompoundIndexes({
  @CompoundIndex(
    name = "idx1_customers",
    def = "{'contactLastName' : 1, 'contactFirstName' : 1}"
  )
})
public class Customers {

  public Customers() {
  }

  @PersistenceConstructor
  public Customers(Long customerNumber, String customerName) {
    this.customerNumber = customerNumber;
    this.customerName = customerName;
  }

  @Id
  private String id;

  @Indexed(name = "pk_customers", direction = IndexDirection.DESCENDING, unique = true)
  @Field("customerNumber")
  private Long customerNumber;

  private String customerName;

  @Field("contactLastName")
  private String contactLastName;

  @Field("contactFirstName")
  private String contactFirstName;

  private String phone;

  private String addressLine1;

  private String addressLine2;

  private String city;

  private String state;

  private String postalCode;

  private String country;

  private Long salesRepEmployeeNumber;

  private BigDecimal creditLimit;

  //...getter/setterは省略します...
}

ポイント

アノテーションを使用してMongoDBのコレクション/ドキュメントとの関係を定義します。

  • @Idアノテーションでドキュメントの_idフィールドをマッピングします。
  • @Documentアノテーションでコレクション名を定義します。
  • @Indexedアノテーションや@CompoundIndexアノテーションでインデックスを定義します。
  • @Fieldアノテーションでドキュメントのフィールドをマッピングします。

Repository

パッケージ: com.example.sbdm.repository

MongoRepositoryインターフェースを継承した各コレクション用のrepositoryインターフェースを作成します。

CustomersRepository

CustomersRepository.java
package com.example.sbdm.repository;

import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;

import com.example.sbdm.domain.Customers;

public interface CustomersRepository extends MongoRepository<Customers, String> {

  public static final String FIND =
   "{$or:[" +
   "{'customerName': {$regex: '?0', $options: 'i'}}," +
   "{'contactFirstName': {$regex: '?0', $options: 'i'}}," +
   "{'contactLastName': {$regex: '?0', $options: 'i'}}" +
   "]}";

  Customers findByCustomerNumber(Long customerNumber);

  Iterable<Customers> findByCustomerNameLike(String customerName, Sort sort);

  Long countByCustomerNameLike(String keyword);

  @Query(value = FIND, count = true)
  Long searchCount(String keyword);

  @Query(value = FIND)
  PageImpl<Customers> search(String keyword, Pageable page);
}

ポイント

  • このrepositoryインターフェースには@Repositoryアノテーションを付けません。付けるとアプリケーション起動時にエラーが発生します。
  • MongoRepositoryインターフェースを継承することで基本的なメソッド(findやcount,save,delete)が使用できるようになります。
  • 任意の条件で検索をおこないたい場合は命名ルールに基づいて独自メソッドを定義します。
  • 任意のクエリーを発行したい場合は@Queryアノテーションを使用して実行するクエリーを指定することができます。

独自メソッドの定義について

命名ルールに従ったメソッド名にすることで任意のクエリーを発行することができます。
ルールは[Query methods] (http://docs.spring.io/spring-data/data-mongo/docs/1.6.3.RELEASE/reference/html/#repositories.query-methods)で詳細に解説されています。

例えば下記のメソッド定義は、customersコレクションのcusotomerNumberフィールドに対してパラメータ値と同じ値を持つドキュメントを検索します。(なおこのフィールドは一意なので、1件しか検索されないことが保証されています。)

customize
Customers findByCustomerNumber(Long customerNumber);

メソッド名にLikeを加えるとlike検索を行います。

customize
Iterable<Customers> findByCustomerNameLike(String customerName, Sort sort);

件数のカウントはcountByを加えます。

customize
Long countByCustomerNameLike(String keyword);

複数のフィールドを検索条件にしたい場合はAndやOrを加えます。

customize
OrderDetails findByOrderNumberAndProductCode(Long orderNumber, String prodctCode);

@Queryアノテーションを使えば、実行したいクエリーを直接指定することができます。
ちなみに、この例ではPageableインタフェースを引数に加えていますが、この場合の戻り値の型はPageImplクラスになります。(加えない場合はArrayListクラスになります。)

customize
@Query(value = FIND)
PageImpl<Customers> search(String keyword, Pageable page);

count = trueを加えるとクエリーの結果をカウントします。

customize
@Query(value = FIND, count = true)
Long searchCount(String keyword);

Service

パッケージ: com.example.sbdm.service

ドキュメント操作を行う基本的なメソッドを定義したインタフェースを作成します。

IService

IService.java
package com.example.sbdm.service;


public interface IService<T> extends Pagination {

  /**
   * コレクションのドキュメントの件数
   */
  public long count();

  /**
   * ドキュメントIDで検索
   */
  public T findById(String id);

  /**
   * 条件なしでドキュメントを検索
   */
  public Iterable<T> findAll(int page, int size, String sortColumn);

  /**
   * ドキュメントのプライマリーキーで検索
   */
  public T findByPk(Object...keys);

  /**
   * 名称のlike検索
   */
  public Iterable<T> findByNameLike(String name, String sortColumn);

  /**
   * 検索ワードに一致するドキュメントの件数
   */
  public long searchCount(String keyword);

  /**
   * 検索ワードに一致するドキュメントを検索
   */
  public Iterable<T> search(String keyword, int page, int size, String sortColumn);

  public T save(T model);

  public Iterable<T> save(Iterable<T> model);

  public void delete(String id);

  public void delete(Iterable<T> model);
}

AbstractService

各コレクション用のサービスクラスの基底クラスをIServiceインターフェースを実装して作成します。

このAbstractServiceクラスでは、似たような機能のメソッドが定義されていますが、その違いは次のようになっています。

  • IServiceインターフェースのメソッドの実装では、上記で説明したrepositoryを使用します。
  • AbstractServiceクラスで定義するメソッドは、MongoTemplateとQueryを使用するメソッドを使用します。
AbstractService.java
package com.example.sbdm.service;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.MongoRepository;

import com.example.sbdm.utils.MongoService;

public abstract class AbstractService<T> implements IService<T> {
  private static Logger logger = LoggerFactory.getLogger(AbstractService.class);

  @Autowired
  private MongoTemplate template;

  /* *
   * repositoryを使用する方法
   * 
   * */

  @Override
  public long count() {
    return getRepository().count();
  }

  @Override
  public T findById(String id) {
    return getRepository().findOne(id);
  }

  @Override
  public Iterable<T> findAll(int page, int size, String sort) {
    Pageable pager = new PageRequest(currentPage(page), size, Direction.ASC, sort);
    Iterable<T> result = getRepository().findAll(pager);
    return result;
  }

  @Override
  public T save(T model) {
    return getRepository().save(model);
  }

  @Override
  public Iterable<T> save(Iterable<T> model) {
    return getRepository().save(model);
  }

  @Override
  public void delete(String id) {
    getRepository().delete(id);
  }

  @Override
  public void delete(Iterable<T> model) {
    getRepository().delete(model);
  }

  abstract protected MongoRepository<T, String> getRepository();


  /* *
   * templateとqueryを使用する方法
   * 
   * */

  protected long doCount(Query query, Class<T> clazz) {
    return template.count(query, clazz);
  }

  protected T doFindOne(Query query, Class<T> clazz) {
    return template.findOne(query, clazz);
  }

  protected List<T> doFind(Query query, Class<T> clazz) {
    return template.find(query, clazz);
  }

  protected List<T> doFindAll(Class<T> clazz) {
    return template.findAll(clazz);
  }

  protected Criteria makeCriteriaById(String id) {
    return Criteria.where("id").is(id);
  }

  protected Criteria makeCriteriaRegex(Criteria criteria, String field, String param) {
    if (criteria == null) {
      criteria = Criteria.where(field).regex(param,"i");
    } else {
      criteria.and(field).regex(param,"i");
    }
    return criteria;
  }

  protected Criteria makeCriteria(Criteria criteria, String field, Object param) {
    if (criteria == null) {
      criteria = Criteria.where(field).is(param);
    } else {
      criteria.and(field).is(param);
    }
    return criteria;
  }

  protected Criteria makeWhere(String name) {
    return Criteria.where(name);
  }

  protected Criteria makeWhere(String name, Object param) {
    return Criteria.where(name).is(param);
  }

  protected Query makeQuery(Criteria criteria) {
    Query query;
    if (criteria != null) {
      query = new Query(criteria);
    } else {
      query = new Query();
    }
    return query;
  }

  protected int calcSkipNum(int page, int size) {
    return (page - 1) * size;
  }

  abstract protected long count(T searchCondition);
  abstract protected List<T> search(int page, int size, Sort sort, T searchCondition);
  abstract protected Criteria makeCriteriaByPk(T model);
  abstract protected Criteria makeCriteria(T model);
  abstract protected Update makeAllUpdate(T model);
}

ポイント

ドキュメントの操作方法には、Repositoryインタフェースを使用する方法と、MongoTemplateとQueryを使用する方法があります。どちらの方法でも同じことができますが、Repositoryを使用する方がコードの実装量が減ります。

  • Repositoryを使用する方法: シンプルなクエリーであればメソッド定義だけで済み、コードの記述やクエリーを意識する必要がありません。
  • MongoTemplateとQueryを使用する方法: プログラムでクエリーを組み立てることができます。

なおどちらの方法でも直接クエリーを実行することができます。

Serviceの具象クラス

パッケージ: com.example.sbdm.service.impl

CustomersService

CustomersService.java
package com.example.sbdm.service.impl;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Service;

import com.example.sbdm.domain.Customers;
import com.example.sbdm.repository.CustomersRepository;
import com.example.sbdm.service.AbstractService;

@Service
public class CustomersService extends AbstractService<Customers> {
  private static Logger logger = LoggerFactory.getLogger(CustomersService.class);

  @Autowired
  private CustomersRepository customersRepository;

  @Override
  protected MongoRepository<Customers, String> getRepository() {
    return customersRepository;
  }

  @Override
  public Customers findByPk(Object...keys) {
    return customersRepository.findByCustomerNumber((Long)keys[0]);
  }

  @Override
  public Iterable<Customers> findByNameLike(String customerName, String sortColumn) {
    Sort sort = new Sort(sortColumn);
    return customersRepository.findByCustomerNameLike(customerName, sort);
  }

  @Override
  public long searchCount(String keyword) {
    return customersRepository.searchCount(keyword);
  }

  @Override
  public Iterable<Customers> search(String keyword, int page, int size, String sortColumn) {
    Pageable pager = new PageRequest(currentPage(page), size, Direction.ASC, sortColumn);
    return customersRepository.search(keyword, pager);
  }

  @Override
  public long count(Customers searchCondition) {
    Criteria criteria = makeCriteria(searchCondition);
    Query query = makeQuery(criteria);
    return doCount(query, Customers.class);
  }

  @Override
  public List<Customers> search(int page, int size, Sort sort, Customers searchCondition) {
    Criteria criteria = makeCriteria(searchCondition);
    Query query= makeQuery(criteria);
    query.skip(calcSkipNum(page, size)).limit(size);
    if (sort != null) {
      query.with(sort);
    }
    return doFind(query, Customers.class);
  }

  @Override
  protected Criteria makeCriteriaByPk(Customers model) {
    return Criteria.where("customerNumber").is(model.getCustomerNumber());
  }

  @Override
  protected Criteria makeCriteria(Customers model) {
    Criteria criteria = null;
    if (model.getCustomerNumber() != null && model.getCustomerNumber() > 0L) {
      criteria = makeCriteria(criteria, "customerNumber", model.getCustomerNumber());
    }
    if (StringUtils.isNotEmpty(model.getCustomerName())) {
      criteria = makeCriteria(criteria, "customerName", model.getCustomerName());
    }
    if (StringUtils.isNotEmpty(model.getPhone())) {
      criteria = makeCriteria(criteria, "phone", model.getPhone());
    }
    if (StringUtils.isNotEmpty(model.getCity())) {
      criteria = makeCriteria(criteria, "city", model.getCity());
    }
    if (StringUtils.isNotEmpty(model.getCountry())) {
      criteria = makeCriteria(criteria, "country", model.getCountry());
    }
    if (StringUtils.isNotEmpty(model.getState())) {
      criteria = makeCriteria(criteria, "state", model.getState());
    }
    if (StringUtils.isNotEmpty(model.getPostalCode())) {
      criteria = makeCriteria(criteria, "postalCode", model.getPostalCode());
    }
    return criteria;
  }

  @Override
  protected Update makeAllUpdate(Customers model) {
    Update update = new Update();
    update.set("customerName", model.getCustomerName());
    update.set("contactLastName", model.getContactLastName());
    update.set("contactFirstName", model.getContactFirstName());
    update.set("phone", model.getPhone());
    update.set("addressLine1", model.getAddressLine1());
    update.set("addressLine2",model.getAddressLine2());
    update.set("city", model.getCity());
    update.set("state", model.getState());
    update.set("postalCode", model.getPostalCode());
    update.set("country", model.getCountry());
    update.set("salesRepEmployeeNumber", model.getSalesRepEmployeeNumber());
    update.set("creditLimit", model.getCreditLimit());
    return update;
  }

}

Controller

パッケージ: com.example.sbdm.web

CustomersController

CustomersController.java
package com.example.sbdm.web;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.sbdm.domain.Customers;
import com.example.sbdm.domain.Orders;
import com.example.sbdm.service.impl.CustomersService;
import com.example.sbdm.service.impl.OrdersService;
import com.example.sbdm.utils.JsonLoader;

@Controller
@RequestMapping(value = "/customers")
public class CustomersController extends BaseController {
  private static Logger logger = LoggerFactory.getLogger(CustomersController.class);

  private static final int PAGE_SIZE = 10;

  @Autowired
  private CustomersService customersService;

  @Autowired
  private OrdersService ordersService;

  @RequestMapping(method = RequestMethod.GET)
  public String _index(Model model) {
    return index(1, null, model);
  }

  @RequestMapping(value = "/", method = RequestMethod.GET)
  public String index(Model model) {
    return index(1, null, model);
  }

  @RequestMapping(value = "/{pageNo}", method = RequestMethod.GET)
  public String index(
      @PathVariable Integer pageNo,
      @RequestParam String keyword,
      Model model) {
    logger.debug("CustomersController:[index] Passing through...");

    int totalCount = 0;
    Iterable<Customers> result;

    if (StringUtils.isNotEmpty(keyword)) {
      result = customersService.search(keyword, pageNo, PAGE_SIZE, "customerName");
      totalCount = (int)customersService.searchCount(keyword);
    } else {
      result = customersService.findAll(pageNo, PAGE_SIZE, "customerNumber");
      totalCount = (int)customersService.count();
    }

    model.addAttribute("keyword", keyword);
    model.addAttribute("result", result);

    addPageAttr(customersService.calcPage(totalCount, pageNo, PAGE_SIZE), model);

    return "Customers/index";
  }

  @RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
  public String detail(
      @PathVariable String id,
      Model model) {
    logger.debug("CustomersController:[detail] Passing through...");

    Customers customer =  customersService.findById(id);

    String json = "{}";
    if (customer != null) {
      json = JsonLoader.toJson(customer);
    }

    Iterable<Orders> orderList = ordersService.findByCustomerNumber(customer.getCustomerNumber());

    model.addAttribute("customer", customer);
    model.addAttribute("orderList", orderList);
    model.addAttribute("json", json);

    return "Customers/detail";
  }

  @RequestMapping(value = "/search", method = RequestMethod.GET)
  public String search(
      @RequestParam(required = false) String country,
      @RequestParam(required = false) String city,
      @RequestParam(required = false) String state,
      @RequestParam(required = false) String postalcode,
      Model model) {
    logger.debug("CustomersController:[search] Passing through...");

    int pageNo = 1;

    Customers searchCondition = new Customers();
    if (StringUtils.isNotEmpty(country)) {
      searchCondition.setCountry(country);
    }
    if (StringUtils.isNotEmpty(city)) {
      searchCondition.setCity(city);
    }
    if (StringUtils.isNotEmpty(state)) {
      searchCondition.setState(state);
    }
    if (StringUtils.isNotEmpty(postalcode)) {
      searchCondition.setPostalCode(postalcode);
    }

    List<Customers> result = customersService.search(pageNo, PAGE_SIZE, null, searchCondition);

    int totalCount = (int)customersService.count(searchCondition);

    model.addAttribute("result", result);

    addPageAttr(customersService.calcPage(totalCount, pageNo, PAGE_SIZE), model);

    return "Customers/index";
  }

}
  • searchアクションの呼び出しは、ブラウザのアドレスバーに直接URLを入力して行います。実行結果は一覧ページに表示されます。

その他のクラスやテンプレート

その他のクラスやテンプレートについては省略します。(MongoDBに関係する重要な部分が無いことと、記事サイズが大きくなってしまうため。)
ソースコード全文はgithub上で公開しています。

実行する

MongoDBが起動されていることを確認し下記のコマンドを実行します。

> mvn spring-boot:run

アプリケーションが起動したら下記のURLにアクセスしページが表示されれば成功です。
ただし、データが1件もない状態なので下図のページが表示されるはずです。

empty.png

データの初期化

初期データはjsonファイルで管理しています。(ファイルはresources/data/initフォルダ下にあります)

resources
 └─data
    └─init
        ├─Customers
        ├─OrderDetails
        ├─Orders
        ├─Payments
        ├─ProductLines
        └─Products

ページのメニューにある"admin:init"をクリックするとjsonファイルをロードしてドキュメントを作成します。
データの初期化が成功すると下図の結果ページが表示されます。

init_result.png

データの初期化が終わったら各ページでデータを確認することができます。

顧客一覧

customers.png

注文一覧

orders.png

製品一覧

products.png

製品種目一覧

productlines.png

支払い一覧

payments.png

53
55
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?