Skip to content

Commit e31ca9b

Browse files
committed
Add example of implementing the MySQL "member of" operator
1 parent eeeba22 commit e31ca9b

File tree

5 files changed

+227
-2
lines changed

5 files changed

+227
-2
lines changed

pom.xml

+14-2
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,13 @@
175175
</dependency>
176176
<dependency>
177177
<groupId>org.testcontainers</groupId>
178-
<artifactId>postgresql</artifactId>
178+
<artifactId>junit-jupiter</artifactId>
179179
<version>${test.containers.version}</version>
180180
<scope>test</scope>
181181
</dependency>
182182
<dependency>
183183
<groupId>org.testcontainers</groupId>
184-
<artifactId>junit-jupiter</artifactId>
184+
<artifactId>postgresql</artifactId>
185185
<version>${test.containers.version}</version>
186186
<scope>test</scope>
187187
</dependency>
@@ -203,6 +203,18 @@
203203
<version>3.5.1</version>
204204
<scope>test</scope>
205205
</dependency>
206+
<dependency>
207+
<groupId>org.testcontainers</groupId>
208+
<artifactId>mysql</artifactId>
209+
<version>${test.containers.version}</version>
210+
<scope>test</scope>
211+
</dependency>
212+
<dependency>
213+
<groupId>com.mysql</groupId>
214+
<artifactId>mysql-connector-j</artifactId>
215+
<version>9.1.0</version>
216+
<scope>test</scope>
217+
</dependency>
206218
</dependencies>
207219

208220
<build>

src/test/java/config/TestContainersConfiguration.java

+1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@
2323
public interface TestContainersConfiguration {
2424
DockerImageName POSTGRES_LATEST = DockerImageName.parse("postgres:17.2");
2525
DockerImageName MARIADB_LATEST = DockerImageName.parse("mariadb:11.6.2");
26+
DockerImageName MYSQL_LATEST = DockerImageName.parse("mysql:9.1.0");
2627
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2016-2025 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+
* https://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 examples.mysql;
17+
18+
import java.util.Objects;
19+
20+
import org.jspecify.annotations.NullMarked;
21+
import org.mybatis.dynamic.sql.AbstractNoValueCondition;
22+
23+
@NullMarked
24+
public class MemberOfCondition<T> extends AbstractNoValueCondition<T> {
25+
private final String jsonArray;
26+
27+
protected MemberOfCondition(String jsonArray) {
28+
this.jsonArray = Objects.requireNonNull(jsonArray);
29+
}
30+
31+
@Override
32+
public String operator() {
33+
return "member of(" + jsonArray + ")";
34+
}
35+
36+
public static <T> MemberOfCondition<T> memberOf(String jsonArray) {
37+
return new MemberOfCondition<>(jsonArray);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2016-2025 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+
* https://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 examples.mysql;
17+
18+
import java.util.Objects;
19+
20+
import org.jspecify.annotations.NullMarked;
21+
import org.mybatis.dynamic.sql.BasicColumn;
22+
import org.mybatis.dynamic.sql.BindableColumn;
23+
import org.mybatis.dynamic.sql.render.RenderingContext;
24+
import org.mybatis.dynamic.sql.select.function.AbstractTypeConvertingFunction;
25+
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
26+
27+
@NullMarked
28+
public class MemberOfFunction<T> extends AbstractTypeConvertingFunction<T, Long, MemberOfFunction<T>> {
29+
30+
private final String jsonArray;
31+
32+
protected MemberOfFunction(BasicColumn column, String jsonArray) {
33+
super(column);
34+
this.jsonArray = Objects.requireNonNull(jsonArray);
35+
}
36+
37+
@Override
38+
protected MemberOfFunction<T> copy() {
39+
return new MemberOfFunction<>(column, jsonArray);
40+
}
41+
42+
@Override
43+
public FragmentAndParameters render(RenderingContext renderingContext) {
44+
return column.render(renderingContext)
45+
.mapFragment(f -> f + " member of(" + jsonArray + ")");
46+
}
47+
48+
public static <T> MemberOfFunction<T> memberOf(BindableColumn<T> column, String jsonArray) {
49+
return new MemberOfFunction<>(column, jsonArray);
50+
}
51+
}
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2016-2025 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+
* https://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 examples.mysql;
17+
18+
import static examples.mysql.MemberOfCondition.memberOf;
19+
import static examples.mysql.MemberOfFunction.memberOf;
20+
import static examples.mariadb.ItemsDynamicSQLSupport.*;
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.entry;
23+
import static org.mybatis.dynamic.sql.SqlBuilder.*;
24+
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
import config.TestContainersConfiguration;
29+
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
30+
import org.apache.ibatis.mapping.Environment;
31+
import org.apache.ibatis.session.Configuration;
32+
import org.apache.ibatis.session.SqlSession;
33+
import org.apache.ibatis.session.SqlSessionFactory;
34+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
35+
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
36+
import org.junit.jupiter.api.BeforeAll;
37+
import org.junit.jupiter.api.Test;
38+
import org.mybatis.dynamic.sql.render.RenderingStrategies;
39+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
40+
import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper;
41+
import org.testcontainers.containers.MySQLContainer;
42+
import org.testcontainers.junit.jupiter.Container;
43+
import org.testcontainers.junit.jupiter.Testcontainers;
44+
45+
@Testcontainers
46+
class MySQLTest {
47+
48+
@SuppressWarnings("resource")
49+
@Container
50+
private static final MySQLContainer<?> mysql =
51+
new MySQLContainer<>(TestContainersConfiguration.MYSQL_LATEST)
52+
.withInitScript("examples/mariadb/CreateDB.sql");
53+
54+
private static SqlSessionFactory sqlSessionFactory;
55+
56+
@BeforeAll
57+
static void setup() {
58+
UnpooledDataSource ds = new UnpooledDataSource(mysql.getDriverClassName(), mysql.getJdbcUrl(),
59+
mysql.getUsername(), mysql.getPassword());
60+
Environment environment = new Environment("test", new JdbcTransactionFactory(), ds);
61+
Configuration config = new Configuration(environment);
62+
config.addMapper(CommonSelectMapper.class);
63+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);
64+
}
65+
66+
@Test
67+
void smokeTest() {
68+
try (SqlSession session = sqlSessionFactory.openSession()) {
69+
CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class);
70+
71+
SelectStatementProvider selectStatement = select(id, description)
72+
.from(items)
73+
.orderBy(id)
74+
.build()
75+
.render(RenderingStrategies.MYBATIS3);
76+
List<Map<String, Object>> rows = mapper.selectManyMappedRows(selectStatement);
77+
assertThat(rows).hasSize(20);
78+
}
79+
}
80+
81+
@Test
82+
void testMemberOfAsCondition() {
83+
try (SqlSession session = sqlSessionFactory.openSession()) {
84+
CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class);
85+
86+
SelectStatementProvider selectStatement = select(id, memberOf(id, "'[1, 2, 3]'").as("inList"))
87+
.from(items)
88+
.where(id, memberOf("'[1, 2, 3]'"))
89+
.orderBy(id)
90+
.build()
91+
.render(RenderingStrategies.MYBATIS3);
92+
93+
assertThat(selectStatement.getSelectStatement())
94+
.isEqualTo("select id, id member of('[1, 2, 3]') as inList from items where id member of('[1, 2, 3]') order by id");
95+
96+
List<Map<String, Object>> rows = mapper.selectManyMappedRows(selectStatement);
97+
assertThat(rows).hasSize(3);
98+
assertThat(rows.get(2)).containsOnly(entry("id", 3), entry("inList", 1L));
99+
}
100+
}
101+
102+
@Test
103+
void testMemberOfAsFunction() {
104+
try (SqlSession session = sqlSessionFactory.openSession()) {
105+
CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class);
106+
107+
SelectStatementProvider selectStatement = select(id, memberOf(id, "'[1, 2, 3]'").as("inList"))
108+
.from(items)
109+
.where(memberOf(id,"'[1, 2, 3]'"), isEqualTo(1L))
110+
.orderBy(id)
111+
.build()
112+
.render(RenderingStrategies.MYBATIS3);
113+
114+
assertThat(selectStatement.getSelectStatement())
115+
.isEqualTo("select id, id member of('[1, 2, 3]') as inList from items where id member of('[1, 2, 3]') = #{parameters.p1} order by id");
116+
117+
List<Map<String, Object>> rows = mapper.selectManyMappedRows(selectStatement);
118+
assertThat(rows).hasSize(3);
119+
assertThat(rows.get(2)).containsOnly(entry("id", 3), entry("inList", 1L));
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)