Skip to content

Commit e90be0c

Browse files
committed
Add examples using Virtual Threads.
Closes #665
1 parent cdefade commit e90be0c

File tree

11 files changed

+366
-4
lines changed

11 files changed

+366
-4
lines changed

README.adoc

+6-2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ Local Elasticsearch instance must be running to run the tests.
2929
== Spring Data JPA
3030

3131
* `eclipselink` - Sample project to show how to use Spring Data JPA with Spring Boot and https://www.eclipse.org/eclipselink/[Eclipselink].
32-
* `example` - Probably the project you want to have a look at first. Contains a variety of sample packages, showcasing the different levels at which you can use Spring Data JPA. Have a look at the `simple` package for the most basic setup.
32+
* `example` - Probably the project you want to have a look at first.
33+
Contains a variety of sample packages, showcasing the different levels at which you can use Spring Data JPA.
34+
Have a look at the `simple` package for the most basic setup.
35+
Contains also examples running on Virtual Threads.
3336
* `interceptors` - Example of how to enrich the repositories with AOP.
3437
* `jpa21` - Shows support for JPA 2.1 specific features (stored procedures support).
3538
* `multiple-datasources` - Examples of how to use Spring Data JPA with multiple `DataSource`s.
3639
* `query-by-example` - Example project showing usage of Query by Example with Spring Data JPA.
3740
* `security` - Example of how to integrate Spring Data JPA Repositories with Spring Security.
38-
* `showcase` - Refactoring show case of how to improve a plain-JPA-based persistence layer by using Spring Data JPA (read: removing close to all of the implementation code). Follow the `demo.txt` file for detailed instructions.
41+
* `showcase` - Refactoring show case of how to improve a plain-JPA-based persistence layer by using Spring Data JPA (read: removing close to all of the implementation code).Follow the `demo.txt` file for detailed instructions.
3942
* `vavr` - Shows the support of https://www.vavr.io[Vavr] collection types as return types for query methods.
4043

4144
== Spring Data LDAP
@@ -72,6 +75,7 @@ Local Elasticsearch instance must be running to run the tests.
7275

7376
* `cluster` - Example for Redis Cluster support.
7477
* `example` - Example for basic Spring Data Redis setup.
78+
* `pubsub` - Example project to show Pub/Sub usage using Platform and Virtual Threads.
7579
* `reactive` - Example project to show reactive template support.
7680
* `repositories` - Example demonstrating Spring Data repository abstraction on top of Redis.
7781
* `sentinel` - Example for Redis Sentinel support.

jpa/example/src/main/java/example/springdata/jpa/simple/SimpleConfiguration.java

+2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package example.springdata.jpa.simple;
1717

1818
import org.springframework.boot.autoconfigure.SpringBootApplication;
19+
import org.springframework.scheduling.annotation.EnableAsync;
1920

2021
/**
2122
* @author Oliver Gierke
2223
*/
2324
@SpringBootApplication
25+
@EnableAsync
2426
class SimpleConfiguration {}

jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.springframework.data.domain.Slice;
2525
import org.springframework.data.domain.Sort;
2626
import org.springframework.data.jpa.repository.Query;
27-
import org.springframework.data.repository.CrudRepository;
27+
import org.springframework.data.repository.ListCrudRepository;
2828
import org.springframework.scheduling.annotation.Async;
2929

3030
/**
@@ -35,7 +35,7 @@
3535
* @author Thomas Darimont
3636
* @author Christoph Strobl
3737
*/
38-
public interface SimpleUserRepository extends CrudRepository<User, Long> {
38+
public interface SimpleUserRepository extends ListCrudRepository<User, Long> {
3939

4040
/**
4141
* Find the user with the given username. This method will be translated into a query using the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.jpa.simple;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.util.List;
21+
import java.util.concurrent.BlockingQueue;
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.LinkedBlockingQueue;
24+
import java.util.concurrent.TimeUnit;
25+
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.api.condition.EnabledOnJre;
29+
import org.junit.jupiter.api.condition.JRE;
30+
31+
import org.springframework.beans.factory.annotation.Autowired;
32+
import org.springframework.boot.test.context.SpringBootTest;
33+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
34+
import org.springframework.transaction.annotation.Propagation;
35+
import org.springframework.transaction.annotation.Transactional;
36+
37+
/**
38+
* Integration test showing the basic usage of {@link SimpleUserRepository} with Virtual Threads.
39+
*
40+
* @author Mark Paluch
41+
*/
42+
@Transactional
43+
@SpringBootTest(properties = "spring.threads.virtual.enabled=true")
44+
@EnabledOnJre(JRE.JAVA_21)
45+
class VirtualThreadsTests {
46+
47+
@Autowired SimpleUserRepository repository;
48+
private User user;
49+
50+
@BeforeEach
51+
void setUp() {
52+
53+
user = new User();
54+
user.setUsername("foobar");
55+
user.setFirstname("firstname");
56+
user.setLastname("lastname");
57+
}
58+
59+
/**
60+
* This repository invocation runs on a dedicated virtual thread.
61+
*/
62+
@Test
63+
@Transactional(propagation = Propagation.NOT_SUPPORTED)
64+
void supportsVirtualThreads() throws Exception {
65+
66+
BlockingQueue<String> thread = new LinkedBlockingQueue<>();
67+
repository.save(new User("Customer1", "Foo"));
68+
repository.save(new User("Customer2", "Bar"));
69+
70+
try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) {
71+
executor.setVirtualThreads(true);
72+
73+
var future = executor.submit(() -> {
74+
thread.add(Thread.currentThread().toString());
75+
return repository.findAll();
76+
});
77+
78+
List<User> users = future.get();
79+
String threadName = thread.poll(1, TimeUnit.SECONDS);
80+
81+
assertThat(threadName).contains("VirtualThread");
82+
assertThat(users).hasSize(2);
83+
}
84+
85+
repository.deleteAll();
86+
}
87+
88+
/**
89+
* Here we demonstrate the usage of {@link CompletableFuture} as a result wrapper for asynchronous repository query
90+
* methods running on Virtual Threads. Note, that we need to disable the surrounding transaction to be able to
91+
* asynchronously read the written data from another thread within the same test method.
92+
*/
93+
@Test
94+
@Transactional(propagation = Propagation.NOT_SUPPORTED)
95+
void asyncUsesVirtualThreads() throws Exception {
96+
97+
BlockingQueue<String> thread = new LinkedBlockingQueue<>();
98+
repository.save(new User("Customer1", "Foo"));
99+
repository.save(new User("Customer2", "Bar"));
100+
101+
var future = repository.readAllBy().thenAccept(users -> {
102+
103+
assertThat(users).hasSize(2);
104+
thread.add(Thread.currentThread().toString());
105+
});
106+
107+
future.join();
108+
String threadName = thread.poll(1, TimeUnit.SECONDS);
109+
110+
assertThat(threadName).contains("VirtualThread");
111+
112+
repository.deleteAll();
113+
}
114+
}

redis/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<modules>
1919
<module>cluster</module>
2020
<module>example</module>
21+
<module>pubsub</module>
2122
<module>reactive</module>
2223
<module>repositories</module>
2324
<module>sentinel</module>

redis/pubsub/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Spring Data Redis Pub/Sub Example
2+
3+
This project contains samples of specific features of Spring Data Redis.
4+

redis/pubsub/pom.xml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<artifactId>spring-data-redis-pubsub</artifactId>
7+
<name>Spring Data Redis - Pub/Sub</name>
8+
9+
<parent>
10+
<groupId>org.springframework.data.examples</groupId>
11+
<artifactId>spring-data-redis-examples</artifactId>
12+
<version>2.0.0.BUILD-SNAPSHOT</version>
13+
<relativePath>../pom.xml</relativePath>
14+
</parent>
15+
16+
<dependencies>
17+
18+
<dependency>
19+
<groupId>${project.groupId}</groupId>
20+
<artifactId>spring-data-redis-example-utils</artifactId>
21+
<version>${project.version}</version>
22+
<scope>test</scope>
23+
</dependency>
24+
25+
</dependencies>
26+
27+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.util.Collection;
21+
import java.util.concurrent.BlockingQueue;
22+
import java.util.concurrent.LinkedBlockingDeque;
23+
import java.util.concurrent.TimeUnit;
24+
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.boot.test.context.SpringBootTest;
29+
import org.springframework.data.redis.connection.RedisConnectionFactory;
30+
import org.springframework.data.redis.core.StringRedisTemplate;
31+
import org.springframework.data.redis.listener.ChannelTopic;
32+
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
33+
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
34+
import org.springframework.data.redis.serializer.StringRedisSerializer;
35+
36+
/**
37+
* Show usage of Redis Pub/Sub operations.
38+
*
39+
* @author Mark Paluch
40+
*/
41+
@SpringBootTest
42+
public class PubSubTests {
43+
44+
@Autowired RedisConnectionFactory connectionFactory;
45+
46+
@Autowired StringRedisTemplate redisTemplate;
47+
48+
@Test
49+
void shouldListenToPubSubEvents() throws Exception {
50+
51+
BlockingQueue<String> events = new LinkedBlockingDeque<>();
52+
53+
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
54+
container.setConnectionFactory(connectionFactory);
55+
container.afterPropertiesSet();
56+
container.addMessageListener(
57+
(message, pattern) -> events.add(String.format("%s@%s", new String(message.getBody()), new String(pattern))),
58+
ChannelTopic.of("my-channel"));
59+
60+
container.start();
61+
62+
redisTemplate.convertAndSend("my-channel", "Hello, world!");
63+
64+
String event = events.poll(5, TimeUnit.SECONDS);
65+
66+
container.stop();
67+
container.destroy();
68+
69+
assertThat(event).isEqualTo("Hello, world!@my-channel");
70+
}
71+
72+
@Test
73+
void shouldNotifyListener() throws Exception {
74+
75+
BlockingQueue<String> events = new LinkedBlockingDeque<>();
76+
77+
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
78+
container.setConnectionFactory(connectionFactory);
79+
container.afterPropertiesSet();
80+
81+
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new MyListener(events));
82+
messageListenerAdapter.afterPropertiesSet();
83+
messageListenerAdapter.setSerializer(StringRedisSerializer.UTF_8);
84+
container.addMessageListener(messageListenerAdapter, ChannelTopic.of("my-channel"));
85+
86+
container.start();
87+
88+
redisTemplate.convertAndSend("my-channel", "Hello, world!");
89+
90+
String event = events.poll(5, TimeUnit.SECONDS);
91+
92+
container.stop();
93+
container.destroy();
94+
95+
assertThat(event).isEqualTo("Hello, world!@my-channel");
96+
}
97+
98+
static class MyListener {
99+
private final Collection<String> events;
100+
101+
public MyListener(Collection<String> events) {
102+
this.events = events;
103+
}
104+
105+
public void handleMessage(String message, String channel) {
106+
events.add(String.format("%s@%s", message, channel));
107+
}
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.util.concurrent.BlockingQueue;
21+
import java.util.concurrent.LinkedBlockingDeque;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.condition.EnabledOnJre;
26+
import org.junit.jupiter.api.condition.JRE;
27+
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.boot.test.context.SpringBootTest;
30+
import org.springframework.core.task.AsyncTaskExecutor;
31+
import org.springframework.data.redis.connection.RedisConnectionFactory;
32+
import org.springframework.data.redis.core.StringRedisTemplate;
33+
import org.springframework.data.redis.listener.ChannelTopic;
34+
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
35+
36+
/**
37+
* Show usage of Redis Pub/Sub operations using Virtual Threads.
38+
*
39+
* @author Mark Paluch
40+
*/
41+
@SpringBootTest(properties = "spring.threads.virtual.enabled=true")
42+
@EnabledOnJre(JRE.JAVA_21)
43+
public class PubSubVirtualThreadsTests {
44+
45+
@Autowired RedisConnectionFactory connectionFactory;
46+
47+
@Autowired AsyncTaskExecutor taskExecutor;
48+
49+
@Autowired StringRedisTemplate redisTemplate;
50+
51+
@Test
52+
void shouldListenToPubSubEvents() throws Exception {
53+
54+
BlockingQueue<String> events = new LinkedBlockingDeque<>();
55+
56+
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
57+
container.setConnectionFactory(connectionFactory);
58+
container.setTaskExecutor(taskExecutor);
59+
container.afterPropertiesSet();
60+
container.addMessageListener(
61+
(message, pattern) -> events
62+
.add(String.format("%s on Thread %s", new String(message.getBody()), Thread.currentThread())),
63+
ChannelTopic.of("my-channel"));
64+
65+
container.start();
66+
67+
redisTemplate.convertAndSend("my-channel", "Hello, world!");
68+
69+
String event = events.poll(5, TimeUnit.SECONDS);
70+
71+
container.stop();
72+
container.destroy();
73+
74+
assertThat(event).isNotNull().contains("Hello, world!").contains("VirtualThread");
75+
}
76+
}

0 commit comments

Comments
 (0)