2025/06/02

Spring Boot 3 quartz

簡單的任務可用 spring task scheduler,複雜的要改用 quartz

pom.xml 要加上 spring-boot-starter-quartz

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

自動設定類別是 QuartzAutoConfiguration,透過註冊的 SchedulerFactoryBean 就能產生 Scheduler。參數綁定類別為 QuartzProperties,參數為 spring.quartz.*

產生 task

只要繼承 QuartzJobBean,實作 executeInternal,就可以產生 quartz 的 Job

SimpleTask.java

package com.test.quartz;

import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

@Slf4j
public class SimpleTask extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) {
        log.info("simple task");
    }

}

設定

  • JobDetail

  • Calendar: 指定/排除特定時間

  • Trigger

TaskConfig.java

package com.test.quartz;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@RequiredArgsConstructor
@Configuration
public class TaskConfig {

    public static final String SIMPLE_TASK = "simple-task";
//    private final SchedulerFactoryBean schedulerFactoryBean;
//
//    @PostConstruct
//    public void init() throws SchedulerException {
//        Scheduler scheduler = schedulerFactoryBean.getScheduler();
//        boolean exists = scheduler.checkExists(JobKey.jobKey(SIMPLE_TASK));
//        if (!exists) {
//            scheduler.scheduleJob(simpleTask(), simpleTaskTrigger());
//        }
//    }

    @Bean
    public JobDetail simpleTask() {
        return JobBuilder.newJob(SimpleTask.class)
                .withIdentity(SIMPLE_TASK)
                .withDescription("simple-task")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger simpleTaskTrigger() {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ? *");
        return TriggerBuilder.newTrigger()
                .withIdentity("simple-task-trigger")
                .forJob(simpleTask())
                .withSchedule(cronScheduleBuilder)
                .build();
    }

}

cron expression 多了最後一個 [<year>] 可以不填寫

<second> <minute> <hour> <day of month> <month> <day of week> [<year>]

ex:

"0/3 * * * * ? 2025-2030" 代表 2025-2030 每 3s 執行一次

啟動後,會看到

org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

設定參數

以下代表將 thread 設定為 5 個

spring:
  quartz:
    properties:
      org:
        quartz:
          threadPool:
            threadCount: 5

persistence

quartz 支援兩種 persistence 方式

  • memory

    預設。每次停止 application 就會把資料丟掉

  • JDBC

pom.xml 加上 JDBC

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testweb
    username: root
    password: password
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: always # always
    overwrite-existing-jobs: true
    properties:
      org:
        quartz:
          threadPool:
            threadCount: 5

initialize-schema 有三種

  • ALWAYS: 每一次都會重建

  • EMBEDDED: 只初始化嵌入式 DB

  • NEVER


動態維護 task

如果 task 太多,會有大量設定的 code,可改用 SchedulerFactoryBean 動態 add/remove task

package com.test.quartz;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@RequiredArgsConstructor
@Configuration
public class TaskConfig {

    public static final String SIMPLE_TASK = "simple-task";
    private final SchedulerFactoryBean schedulerFactoryBean;

    @PostConstruct
    public void init() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        boolean exists = scheduler.checkExists(JobKey.jobKey(SIMPLE_TASK));
        if (!exists) {
            scheduler.scheduleJob(simpleTask(), simpleTaskTrigger());
        }
    }

//    @Bean
    public JobDetail simpleTask() {
        return JobBuilder.newJob(SimpleTask.class)
                .withIdentity(SIMPLE_TASK)
                .withDescription("simple-task")
                .storeDurably()
                .build();
    }

//    @Bean
    public Trigger simpleTaskTrigger() {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ? *");
        return TriggerBuilder.newTrigger()
                .withIdentity("simple-task-trigger")
                .forJob(simpleTask())
                .withSchedule(cronScheduleBuilder)
                .build();
    }

}

2025/05/26

Spring Boot 3 Scheduler

spring 的 scheduler task 主要需要 spring-context library,因為任何一個 spring boot start 都有包含 spring-context,所以都有支援 scheduler。

spring 提供自動設定類別

  • TaskExecutionAutoConfiguration

  • TaskSchedulingAutoConfiguration

註冊在 org.springframework.boot.autoconfigure.AutoConfiguration.imports 檔案中

如果 context 裡面沒有任何 Executor instance,就會自動註冊一個預設的 ThreadPoolTaskExecutor,該類別是使用 java.util.concurrent.Executor interface 實作自己的 task executor。同時註冊了一個 TaskExecutorBuilder instance,用來實作自訂的 task executor thread pool。

參數綁定類別是 TaskExecutionProperties

參數是 spring.task.execution.*


如果 context 裡面沒有任何一個 instance

  • SchedulerConfigurer

    org.springframework.scheduling.annotation

  • TaskScheduler

    org.springframework.scheduling

  • SchedulerdExecutorService

    java.util.concurrent

就會自動註冊一個 ThreadPoolTaskScheduler instance。

可以註冊一個 TaskSchedulerBuilder instance,參數對應綁定類別是 TaskSchedulingProperties。參數是 spring.task.scheduling.*


  • thread size < core thread size(coreSize)

    • 產生新的 thread 執行 task
  • thread size = core thread size(coreSize) 且 queueCapacity is not full

    • 將 task 放入 queue
  • thread size = core thread size(coreSize) 且 queueCapacity is full 且 thread size < max thread size (maxSize)

    • 產生新的 thread 執行 task
  • coreSize, queueCapacity, maxSize 都滿了

    • 拒絕執行 task

    • 預設為 AbortPolicy (ref: ExecutorConfigurationSupport)

  • thread 空閒時間到達 keepAlive 時

    • 刪除 thread

    • 預設只會刪除 core thread 以外的 threads

    • allowCoreThreadTimeout 可控制是否要刪除 core thread


spring task 使用起來比 quartz 簡單

先在 application 加上 @EnableScheduling

@EnableScheduling
@SpringBootApplication
public class TaskApplication {

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

}
  • @EnableScheduling

    表示啟用 scheduling,會引用 SchedulingConfiguraiton 自動設定,這是透過註冊 ScheduledAnnotationBeanPostProcessor instance 實現

  • @EnableAsync

    啟用 task 非同步運作,這是引用 AsyncConfigurationSelector,根據不同的 AOP 選擇不同的設定類別

這兩個都屬於 spring-context 的 annotation,不需要其他 library


SimpleTask

SmpleTask.java

package com.test.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class SimpleTask {

    @Async
    @Scheduled(cron = "*/3 * * * * *")
    public void printTask() {
        log.info("SimpleTask is working ...");
    }

}

執行時,固定會以 "scheduling-1" 這個 thread 執行

[   scheduling-1] com.test.task.SimpleTask                 : SimpleTask is working ...

沒有用到 ThreadPoolTaskExecutor


修改 application,加上 @EnableAsync,才會使用到 ThreadPoolTaskExecutor

@EnableScheduling
@EnableAsync
@SpringBootApplication
public class TaskApplication {

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

}

執行結果,會是 task-1 ~ task-8,然後一直重複。 ThreadPoolTaskExecutor 預設有 8 個 coreThread

[         task-1] com.test.task.SimpleTask                 : SimpleTask is working ...
[         task-2] com.test.task.SimpleTask                 : SimpleTask is working ...

cron

<second> <minute> <hour> <day of month> <month> <day of week>
  • second: 0~59

  • minute: 0~59

  • hour: 0~23

  • day of month: 1~31

  • month: 1~12 或 JAN ~ DEC

  • day of week: 0~7,0 代表週日,或是 MON ~ SUN

example:

cron expression desc
*/3 * * * * * 每 3s 執行一次
0 0 * * * * 每小時 0分 0 秒執行一次
0 0 9~16 * * * 每天 9 ~ 16 點整點執行一次
0 0 9,16 * * * 每天 9,16 點執行一次
0 0/30 9-16 * * * 每天 9 ~ 16 點,每 30 分執行一次

支援用以下的寫法替代

  • @yearly 等同 0 0 0 1 1 *

  • @monthly

  • @weekly

  • @daily

  • @hourly

cron expression 不支援年


自訂 thread pool

修改系統設定參數

application.yml

spring:
  task:
    execution:
      pool:
        # core thread size
        coreSize: 5
        # max thread size
        maxSize: 10
        # max queue size
        queueCapacity: 50
        # thread keep alive for 10s
        keepAlive: 10s
        #
        allowCoreThreadTimeout: false
    scheduling:
      pool:
        size: 3

拒絕的策略無法透過參數設定修改,只能使用預設的 AbortPolicy


自訂 thread pool bean

TaskConfig.java 產生兩個 thread pool: taskExecutor1, taskExecutor2

package com.test.task;

import org.springframework.boot.autoconfigure.task.TaskExecutionProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.time.Duration;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class TaskConfig {

    @Lazy
    @Bean
    @Primary
    public ThreadPoolTaskExecutor taskExecutor1(TaskExecutionProperties taskExecutionProperties) {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        TaskExecutionProperties.Pool pool = taskExecutionProperties.getPool();
        map.from(pool::getQueueCapacity).to(taskExecutor::setQueueCapacity);
        map.from(pool::getCoreSize).to(taskExecutor::setCorePoolSize);
        map.from(pool::getMaxSize).to(taskExecutor::setMaxPoolSize);
        map.from(pool::getKeepAlive).asInt(Duration::getSeconds).to(taskExecutor::setKeepAliveSeconds);
        map.from(pool::isAllowCoreThreadTimeout).to(taskExecutor::setAllowCoreThreadTimeOut);
        map.from("my-task1-").whenHasText().to(taskExecutor::setThreadNamePrefix);

        // default: AbortPolicy
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

        return taskExecutor;
    }

    @Lazy
    @Bean
    public ThreadPoolTaskExecutor taskExecutor2(TaskExecutionProperties taskExecutionProperties) {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        TaskExecutionProperties.Pool pool = taskExecutionProperties.getPool();
        map.from(10).to(taskExecutor::setQueueCapacity);
        map.from(3).to(taskExecutor::setCorePoolSize);
        map.from(5).to(taskExecutor::setMaxPoolSize);
        map.from(20).to(taskExecutor::setKeepAliveSeconds);
        map.from(true).to(taskExecutor::setAllowCoreThreadTimeOut);
        map.from("my-task2-").whenHasText().to(taskExecutor::setThreadNamePrefix);

        return taskExecutor;
    }
}

修改剛剛的 SimpleTask

@Async("taskExecutor2") 加上 taskExecutor2,指定 thread pool

@Slf4j
@Component
public class SimpleTask {

    @Async("taskExecutor2")
    @Scheduled(cron = "*/3 * * * * *")
    public void printTask() {
        log.info("SimpleTask is working ...");
    }

}

執行結果

[     my-task2-1] com.test.task.SimpleTask                 : SimpleTask is working ...
[     my-task2-2] com.test.task.SimpleTask                 : SimpleTask is working ...
[     my-task2-3] com.test.task.SimpleTask                 : SimpleTask is working ...
[     my-task2-1] com.test.task.SimpleTask                 : SimpleTask is working ...

2025/05/12

Spring Boot 3 Data 2

Spring Data JPA

JPA: Java Persistence API,提供 POJO 物件持久化的標準規格,可把 Java 物件直接映射到資料庫中

JPA 是規格,Hibermate, TopLink, OpenJPA 都是 JPA 的實作。目前以 Hibernate 為 Java 最流行的 JPA framework。

Spring Data JPA 底層也是使用 Hibernate,最後透過 JDBC 連接資料庫。

spring-boot-starter-data-jpa 自動設定了 JPA,自動設定類別是

  • JpaRepositoriesAutoConfiguration

  • HibernateJpaAutoConfiguration

pom.xml

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-community-dialects</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

application.yml

spring:
  application:
    name: data2

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost/testweb
    username: root
    password: password

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        # dialect: org.hibernate.dialect.MySQLDialect
        dialect: org.hibernate.community.dialect.MySQLLegacyDialect

傳統的 JPA 必需要設定 persistence.xml,但在 spring boot 不需要。

Entity class 只需要有 @Entity@SpringBootApplication 或是 @EnableAutoConfiguration 就會自動掃描所有 @Entity

Data2Application 直接啟動即可

@SpringBootApplication
public class Data2Application {

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

}

UserController.java

package com.test.data2.controller;

import com.test.data2.entity.UserDO;
import com.test.data2.repo.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class UserController {

    private final UserRepository userRepository;

    @GetMapping("/user/info/{id}")
    public UserDO getUserInfo(@PathVariable("id") long id){
        UserDO userDO = userRepository.findById(id).orElseGet(null);
        return userDO;
    }

}

UserRepository.java

package com.test.data2.repo;

import com.test.data2.entity.UserDO;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<UserDO, Long> {

}

UserDO.java

package com.test.data2.entity;

import jakarta.persistence.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
@Entity(name = "user")
public class UserDO implements Serializable {

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

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String phone;

    @Column(name = "create_time", nullable = false)
    private Date createTime;

    @Column(nullable = false)
    private Integer status;

}

啟動後,可從網址 http://localhost:8080/user/info/2 取得 userid = 2 的資料

console 會出現 sql

Hibernate: select ud1_0.id,ud1_0.create_time,ud1_0.phone,ud1_0.status,ud1_0.username from user ud1_0 where ud1_0.id=?

note:

如果掃描不到,可加上 @EntityScan 指定 package/class

Spring data JPA 的 db operation 稱為 repository

  • org.springframework.data.repository.CrudRepository

  • org.springframework.data.repository.ListCrudRepository

  • org.springframework.data.repository.ListPagingRepository

  • org.springframework.data.repository.PagingAndSortingRepository

  • org.springframework.data.repository.kotlin.*

  • org.springframework.data.repository.reactive.*

UserRepository interface 會被自動掃描到,在 controller 能自動注入使用


MyBatis

MyBatis是一個Java持久化框架,它通過 XML 或 annotation 把物件與儲存程序或SQL語句關聯起來,對映成資料庫內對應的 record。

MyBatis-Plus 是 MyBatis 的封裝。


Redis

REmote DIctionary Server: Redis,是一種 key-value 的 NoSQL memory database。通常用在 cache 資料。

spring-boot-starter-data-redis

org.springframework.boot.autoconfigure.AutoConfiguration.imports 有註冊幾個自動設定類別

  • RedisAutoConfiguration

  • RedisReactiveAutoConfiguration

  • RedisRepositoriesAutoConfiguration

RedisAutoConfiguration 對應的參數綁定類別是 RedisProperties

在 application.yml 設定即可使用

spring:
  application:
    name: data3
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      password: password
#      client-type: jedis

RedisTemplate

有兩個預設的模板可直接使用

  • RedisTemplate

    可處理 <Object, Object>

  • StringRedisTemplate

    只能處理 <String, String> 的資料,預設使用 JDK 的 serializer

因為 StringRedisTemplate 繼承自 RedisTemplate,必須設定兩個 RedisTemplate,所以要搭配 @ConditionalOnSingleCandidate 注入

RedisTemplate 提供的 interface:

interface desc
GeoOperations 地理空間
HashOperations Hash data
HyperLogLogOperations HyperLogLog
ListOperations List
SetOperations Set
ValueOperations String
ZSetOperations Ordered Set

設定 pom.xml,要加上 spring-boot-starter-integration, spring-integration-redis

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

要新增一個連線,注入 StringRedisTemplate 使用

package com.test.data3;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class RedisController2 {

    private final StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/redis2/set")
    public String set(@RequestParam("name") String name, @RequestParam("value") String value) {
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
        valueOperations.set(name, value);
        return valueOperations.get(name);
    }

}

呼叫網址 http://localhost:8080/redis2/set?name=test&value=12345 ,就會設定一個 redis key,可使用 redis-cli 檢查

> redis-cli -a password
127.0.0.1:6379> keys *
1) "test"
127.0.0.1:6379> get test
"12345"

也可以直接注入對應的 RedisTemplate 使用 valueOperations

@RequiredArgsConstructor
@RestController
public class RedisController3 {

//    private final StringRedisTemplate stringRedisTemplate;

    @Resource(name = "stringRedisTemplate")
    private ValueOperations<String, String> valueOperations;

    @RequestMapping("/redis3/set")
    public String set(@RequestParam("name") String name, @RequestParam("value") String value) {
        valueOperations.set(name, value);
        return valueOperations.get(name);
    }

}

自訂 RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        RedisSerializer jacksonSerializer = getJacksonSerializer();

        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jacksonSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        template.setEnableTransactionSupport(true);
        template.afterPropertiesSet();

        return template;
    }

    private RedisSerializer getJacksonSerializer() {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL);
        return new GenericJackson2JsonRedisSerializer(om);
    }
}

這裡 key 使用了 StringRedisSerializer, value 使用了 GenericJackson2JsonRedisSerializer

以下是幾個常用的 serializer

serializer desc
StringRedisSerializer String/byte[] 轉換器
JdkSerializationRedisSerializer JDK serializer
OxmSerializer XML serializer
Jackson2JsonRedisSerializer JSON serializer, 需要定義 JavaType
GenericJackson2JsonRedisSerializer JSON serializer, 不需要定義 JavaType

MongoDB

springboot 提供兩種 starter

  • spring-boot-starter-data-mongodb

  • spring-boot-starter-data-mongodb-reactive

首先修改 pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

在 org.springframework.boot.autoconfigure.AutoConfiguration.imports 註冊了幾個自動設定類別

  • MongoDataAutoConfiguration

  • MongoReactiveDataAutoConfiguration

  • MongoRepositoriesAutoConfiguration

  • MongoAutoConfiguration

  • MongoReactiveAutoConfiguration

設定 application.yml

spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/test

使用 mongoTemplate

@RequiredArgsConstructor
@RestController
public class MongoController {

    public static final String COLLECTION_NAME = "test";
    private final MongoTemplate mongoTemplate;

    @RequestMapping("/mongo/insert")
    public User insert(@RequestParam("name") String name, @RequestParam("sex") int sex) {
        // add
        User user = new User(RandomUtils.nextInt(), name, sex);
        mongoTemplate.insert(user, COLLECTION_NAME);

        // query
        Query query = new Query(Criteria.where("name").is(name));
        return mongoTemplate.findOne(query, User.class, COLLECTION_NAME);
    }
}

新增 User.java

package com.test.data4;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.mongodb.core.mapping.Document;

@NoArgsConstructor
@AllArgsConstructor
@Data
@Document(collection = "test")
public class User {
    private long id;
    private String name;
    private int sex;

}

發送網址 http://localhost:8080/mongo/insert?name=test&sex=1

就會 insert 一筆資料到 mongodb collection: test

/* 1 */
{
    "_id" : NumberLong(708252679),
    "name" : "test",
    "sex" : 1,
    "_class" : "com.test.data4.User"
}

MongoRepository

新增 UserRepository

package com.test.data4;

import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface UserRepository extends MongoRepository<User, Long> {

    List<User> findByName(String name);

}

修改 MongoController.java

    private final UserRepository userRepository;

    @RequestMapping("/mongo/insert2")
    public User repoInsert(@RequestParam("name") String name, @RequestParam("sex") int sex) {
        // add
        User user = new User(RandomUtils.nextInt(), name, sex);
        userRepository.save(user);

        // query
        return userRepository.findByName(name).get(0);
    }

發送網址 http://localhost:8080/mongo/insert2?name=test2&sex=1

就會新增 test2


Elasticsearch

springboot 支援3種客戶端

  • Elasticsearch 官方 REST 客戶端

  • Elasticsearch 官方 Java API Client

  • spring data elasticsearch 提供的 ReactiveElasticsearchClient

pom.xml

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

在 org.spingframework.boot.autoconfigure.AutoConfiguration.imports 註冊了

  • ElasticsearchDataAutoConfiguration

  • ElasticsearchRepositoriesAutoConfiguration

  • ReactiveElasticsearchRepositoriesAutoConfiguration

  • ElasticsearchClientAutoConfiguration

  • ElasticsearchRestClientAutoConfiguration

  • ReactiveElasticsearchClientAutoConfiguration

參數綁定 ElasticsearchProperties

參數 spring.elasticsearch.*

application.yml

spring:
  elasticsearch:
    uris: http://localhost:9200
    connection-timeout: 5s
    socket-timeout: 10s
    username: elastic
    password: password

這邊是用關閉 xpack,使用 http 的方式連線

改用 https 可參考

ref: spring-boot-starter-data-elasticsearch es带x-pack后台配置 - 微信公众号-醉鱼Java - 博客园


使用 ElasticsearchTemplate

EsController.java

import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class EsController {

    public static final String INDEX_JAVA = "java";
    private final ElasticsearchTemplate elasticsearchTemplate;

    @RequestMapping("/es/insert")
    public User insert(@RequestParam("name") String name, @RequestParam("sex") int sex) throws InterruptedException {
        // add
        User user = new User(RandomUtils.nextInt(), name, sex);
        IndexCoordinates indexCoordinates =  IndexCoordinates.of(INDEX_JAVA);
        User save = elasticsearchTemplate.save(user, indexCoordinates);

        // delay and query
        Thread.sleep(1000l);
        Query query = new CriteriaQuery(Criteria.where("name").is(name));
        return elasticsearchTemplate.searchOne(query, User.class, indexCoordinates).getContent();
    }
}

User.java

@NoArgsConstructor
@AllArgsConstructor
@Data
@Document(indexName = "java")
public class User {
    private long id;
    private String name;
    private int sex;

}

使用 UserRepository

修改 EsController.java

    private final UserRepository userRepository;
    @RequestMapping("/es/repo/insert")
    public User repoInsert(@RequestParam("name") String name, @RequestParam("sex") int sex) {
        // add
        User user = new User(RandomUtils.nextInt(), name, sex);
        userRepository.save(user);

        // query
        return userRepository.findByName(name).get(0);
    }

UserRepository.java

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface UserRepository extends ElasticsearchRepository<User, Long> {

    List<User> findByName(String name);

}

程式跟 MongoDB 類似