概要
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
参考
下記のサイトを参考にさせていただきました。
- [Spring Data MongoDB - Reference Documentation] (http://docs.spring.io/spring-data/data-mongo/docs/1.6.3.RELEASE/reference/html/)
完成図
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でアプリケーションの雛形を作成
> mvn archetype:generate -DgroupId=com.example.sbdm -DartifactId=sbdm-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
> cd sbdm-example
> mvn eclipse:eclipse
eclipseにインポート
- メニューバーの"File" -> "Import..." -> "Maven" -> "Existing Maven Projects"を選択します。
- プロジェクトのディレクトリを選択し、"Finish"ボタンをクリックします。
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を作成します。
# 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"に指定しました。
<?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>
ビルド
この時点で動作検証を兼ねてビルドします。
> mvn package
ビルドが成功したら生成したjarファイルを実行します。
コマンドプロンプトに"Hello World!"と表示されれば成功です。
> cd target
> java -jar sbdm-example-1.0-SNAPSHOT.jar
Hello World!
アプリケーションの開発
すべてのソースコードを掲載すると記事サイズが大きくなってしまうのでポイントとなる部分だけ掲載していきます。
App
エンドポイントとなるクラスを作成します。
すでにサンプルの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
を継承して作成します。
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
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
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件しか検索されないことが保証されています。)
Customers findByCustomerNumber(Long customerNumber);
メソッド名にLikeを加えるとlike検索を行います。
Iterable<Customers> findByCustomerNameLike(String customerName, Sort sort);
件数のカウントはcountByを加えます。
Long countByCustomerNameLike(String keyword);
複数のフィールドを検索条件にしたい場合はAndやOrを加えます。
OrderDetails findByOrderNumberAndProductCode(Long orderNumber, String prodctCode);
@Query
アノテーションを使えば、実行したいクエリーを直接指定することができます。
ちなみに、この例ではPageableインタフェースを引数に加えていますが、この場合の戻り値の型はPageImpl
クラスになります。(加えない場合はArrayList
クラスになります。)
@Query(value = FIND)
PageImpl<Customers> search(String keyword, Pageable page);
count = true
を加えるとクエリーの結果をカウントします。
@Query(value = FIND, count = true)
Long searchCount(String keyword);
Service
パッケージ: com.example.sbdm.service
ドキュメント操作を行う基本的なメソッドを定義したインタフェースを作成します。
IService
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を使用するメソッドを使用します。
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
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
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件もない状態なので下図のページが表示されるはずです。
データの初期化
初期データはjsonファイルで管理しています。(ファイルはresources/data/init
フォルダ下にあります)
resources
└─data
└─init
├─Customers
├─OrderDetails
├─Orders
├─Payments
├─ProductLines
└─Products
ページのメニューにある"admin:init"をクリックするとjsonファイルをロードしてドキュメントを作成します。
データの初期化が成功すると下図の結果ページが表示されます。
データの初期化が終わったら各ページでデータを確認することができます。
顧客一覧
注文一覧
製品一覧
製品種目一覧
支払い一覧