Skip to content

Commit 9ee6941

Browse files
authored
Merge pull request #2477 from bzhou/master
support for JDK 16 record type
2 parents 1f021fb + 136595c commit 9ee6941

File tree

8 files changed

+334
-4
lines changed

8 files changed

+334
-4
lines changed

pom.xml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,16 @@
374374
</excludes>
375375
</configuration>
376376
</plugin>
377+
<plugin>
378+
<groupId>org.codehaus.mojo</groupId>
379+
<artifactId>animal-sniffer-maven-plugin</artifactId>
380+
<configuration>
381+
<ignores>
382+
<!-- https://github.com/mojohaus/animal-sniffer/issues/67 -->
383+
<ignore>java.lang.invoke.MethodHandle</ignore>
384+
</ignores>
385+
</configuration>
386+
</plugin>
377387
</plugins>
378388

379389
<resources>
@@ -415,6 +425,38 @@
415425
<excludedGroups />
416426
</properties>
417427
</profile>
428+
<profile>
429+
<id>pre16</id>
430+
<activation>
431+
<jdk>(,16)</jdk>
432+
</activation>
433+
<build>
434+
<pluginManagement>
435+
<plugins>
436+
<plugin>
437+
<groupId>org.apache.maven.plugins</groupId>
438+
<artifactId>maven-compiler-plugin</artifactId>
439+
<configuration>
440+
<testExcludes>
441+
<testExclude>**/record_type/*.java</testExclude>
442+
</testExcludes>
443+
</configuration>
444+
</plugin>
445+
</plugins>
446+
</pluginManagement>
447+
</build>
448+
</profile>
449+
<profile>
450+
<id>16</id>
451+
<activation>
452+
<jdk>[16,)</jdk>
453+
</activation>
454+
<properties>
455+
<maven.compiler.testTarget>16</maven.compiler.testTarget>
456+
<maven.compiler.testSource>16</maven.compiler.testSource>
457+
<excludedGroups>TestcontainersTests,RequireIllegalAccess</excludedGroups>
458+
</properties>
459+
</profile>
418460
</profiles>
419461

420462
</project>

src/main/java/org/apache/ibatis/reflection/Reflector.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2021 the original author or authors.
2+
* Copyright 2009-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
1515
*/
1616
package org.apache.ibatis.reflection;
1717

18+
import java.lang.invoke.MethodHandle;
19+
import java.lang.invoke.MethodHandles;
20+
import java.lang.invoke.MethodType;
1821
import java.lang.reflect.Array;
1922
import java.lang.reflect.Constructor;
2023
import java.lang.reflect.Field;
@@ -50,6 +53,7 @@
5053
*/
5154
public class Reflector {
5255

56+
private static final MethodHandle isRecordMethodHandle = getIsRecordMethodHandle();
5357
private final Class<?> type;
5458
private final String[] readablePropertyNames;
5559
private final String[] writablePropertyNames;
@@ -65,9 +69,13 @@ public Reflector(Class<?> clazz) {
6569
type = clazz;
6670
addDefaultConstructor(clazz);
6771
Method[] classMethods = getClassMethods(clazz);
68-
addGetMethods(classMethods);
69-
addSetMethods(classMethods);
70-
addFields(clazz);
72+
if (isRecord(type)) {
73+
addRecordGetMethods(classMethods);
74+
} else {
75+
addGetMethods(classMethods);
76+
addSetMethods(classMethods);
77+
addFields(clazz);
78+
}
7179
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
7280
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
7381
for (String propName : readablePropertyNames) {
@@ -78,6 +86,11 @@ public Reflector(Class<?> clazz) {
7886
}
7987
}
8088

89+
private void addRecordGetMethods(Method[] methods) {
90+
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0)
91+
.forEach(m -> addGetMethod(m.getName(), m, false));
92+
}
93+
8194
private void addDefaultConstructor(Class<?> clazz) {
8295
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
8396
Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
@@ -445,4 +458,25 @@ public boolean hasGetter(String propertyName) {
445458
public String findPropertyName(String name) {
446459
return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
447460
}
461+
462+
/**
463+
* Class.isRecord() alternative for Java 15 and older.
464+
*/
465+
private static boolean isRecord(Class<?> clazz) {
466+
try {
467+
return isRecordMethodHandle != null && (boolean)isRecordMethodHandle.invokeExact(clazz);
468+
} catch (Throwable e) {
469+
throw new ReflectionException("Failed to invoke 'Class.isRecord()'.", e);
470+
}
471+
}
472+
473+
private static MethodHandle getIsRecordMethodHandle() {
474+
MethodHandles.Lookup lookup = MethodHandles.lookup();
475+
MethodType mt = MethodType.methodType(boolean.class);
476+
try {
477+
return lookup.findVirtual(Class.class, "isRecord", mt);
478+
} catch (NoSuchMethodException | IllegalAccessException e) {
479+
return null;
480+
}
481+
}
448482
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--
2+
-- Copyright 2009-2022 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+
17+
drop table prop if exists;
18+
19+
create table prop (
20+
id int,
21+
val varchar(20),
22+
url varchar(32)
23+
);
24+
25+
insert into prop (id, val, url) values (1, 'Val1', 'https://www.google.com');
26+
27+
create table item (
28+
id int,
29+
prop_id int
30+
);
31+
32+
insert into item (id, prop_id) values (100, 1);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright 2009-2022 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+
17+
package org.apache.ibatis.submitted.record_type;
18+
19+
public record Item(Integer id, Property property) {
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2009-2022 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 org.apache.ibatis.submitted.record_type;
17+
18+
public record Property(int id, String value, String URL) {
19+
public String value() {
20+
// Differentiate between method call and field access
21+
return value + "!";
22+
}
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2009-2022 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 org.apache.ibatis.submitted.record_type;
17+
18+
import org.apache.ibatis.annotations.Arg;
19+
import org.apache.ibatis.annotations.Insert;
20+
import org.apache.ibatis.annotations.Results;
21+
import org.apache.ibatis.annotations.Select;
22+
23+
public interface RecordTypeMapper {
24+
25+
@Select("select id, val, url from prop where id = #{id}")
26+
Property selectPropertyAutomapping(int id);
27+
28+
@Results(id = "propertyRM")
29+
@Arg(column = "id", javaType = int.class)
30+
@Arg(column = "val", javaType = String.class)
31+
@Arg(column = "url", javaType = String.class)
32+
@Select("select val, id, url from prop where id = #{id}")
33+
Property selectProperty(int id);
34+
35+
@Insert("insert into prop (id, val, url) values (#{id}, #{value}, #{URL})")
36+
int insertProperty(Property property);
37+
38+
@Arg(id = true, column = "id", javaType = Integer.class)
39+
@Arg(javaType = Property.class, resultMap = "propertyRM", columnPrefix = "p_")
40+
@Select({
41+
"select i.id, p.id p_id, p.val p_val, p.url p_url",
42+
"from item i left join prop p on p.id = i.prop_id",
43+
"where i.id = #{id}" })
44+
Item selectItem(Integer id);
45+
46+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2009-2022 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 org.apache.ibatis.submitted.record_type;
17+
18+
import static org.junit.jupiter.api.Assertions.*;
19+
20+
import java.io.Reader;
21+
22+
import org.apache.ibatis.BaseDataTest;
23+
import org.apache.ibatis.io.Resources;
24+
import org.apache.ibatis.session.SqlSession;
25+
import org.apache.ibatis.session.SqlSessionFactory;
26+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
27+
import org.junit.jupiter.api.BeforeAll;
28+
import org.junit.jupiter.api.Test;
29+
30+
class RecordTypeTest {
31+
32+
private static SqlSessionFactory sqlSessionFactory;
33+
34+
@BeforeAll
35+
static void setUp() throws Exception {
36+
// create a SqlSessionFactory
37+
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/record_type/mybatis-config.xml")) {
38+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
39+
}
40+
// populate in-memory database
41+
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
42+
"org/apache/ibatis/submitted/record_type/CreateDB.sql");
43+
}
44+
45+
@Test
46+
void testSelectRecord() {
47+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
48+
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
49+
Property prop = mapper.selectProperty(1);
50+
assertEquals("Val1!", prop.value());
51+
assertEquals("https://www.google.com", prop.URL());
52+
}
53+
}
54+
55+
@Test
56+
void testSelectRecordAutomapping() {
57+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
58+
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
59+
Property prop = mapper.selectPropertyAutomapping(1);
60+
assertEquals("Val1!", prop.value());
61+
assertEquals("https://www.google.com", prop.URL());
62+
}
63+
}
64+
65+
@Test
66+
void testInsertRecord() {
67+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
68+
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
69+
assertEquals(1, mapper.insertProperty(new Property(2, "Val2", "https://mybatis.org")));
70+
sqlSession.commit();
71+
}
72+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
73+
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
74+
Property prop = mapper.selectProperty(2);
75+
assertEquals("Val2!!", prop.value());
76+
assertEquals("https://mybatis.org", prop.URL());
77+
}
78+
}
79+
80+
@Test
81+
void testSelectNestedRecord() {
82+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
83+
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
84+
Item item = mapper.selectItem(100);
85+
assertEquals(Integer.valueOf(100), item.id());
86+
assertEquals(new Property(1, "Val1", "https://www.google.com"), item.property());
87+
}
88+
}
89+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!--
3+
4+
Copyright 2009-2022 the original author or authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<!DOCTYPE configuration
20+
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
21+
"http://mybatis.org/dtd/mybatis-3-config.dtd">
22+
23+
<configuration>
24+
25+
<environments default="development">
26+
<environment id="development">
27+
<transactionManager type="JDBC">
28+
<property name="" value="" />
29+
</transactionManager>
30+
<dataSource type="UNPOOLED">
31+
<property name="driver" value="org.hsqldb.jdbcDriver" />
32+
<property name="url"
33+
value="jdbc:hsqldb:mem:record_type" />
34+
<property name="username" value="sa" />
35+
</dataSource>
36+
</environment>
37+
</environments>
38+
39+
<mappers>
40+
<mapper
41+
class="org.apache.ibatis.submitted.record_type.RecordTypeMapper" />
42+
</mappers>
43+
44+
</configuration>

0 commit comments

Comments
 (0)