Skip to content

Commit eabec48

Browse files
committed
Support repeatable annotation on @Arg and @Result
1 parent b1a1a78 commit eabec48

File tree

7 files changed

+215
-6
lines changed

7 files changed

+215
-6
lines changed

src/main/java/org/apache/ibatis/annotations/Arg.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package org.apache.ibatis.annotations;
1717

1818
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Repeatable;
1921
import java.lang.annotation.Retention;
2022
import java.lang.annotation.RetentionPolicy;
2123
import java.lang.annotation.Target;
@@ -32,7 +34,8 @@
3234
*/
3335
@Documented
3436
@Retention(RetentionPolicy.RUNTIME)
35-
@Target({})
37+
@Target(ElementType.METHOD)
38+
@Repeatable(ConstructorArgs.class)
3639
public @interface Arg {
3740

3841
/**

src/main/java/org/apache/ibatis/annotations/Result.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package org.apache.ibatis.annotations;
1717

1818
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Repeatable;
1921
import java.lang.annotation.Retention;
2022
import java.lang.annotation.RetentionPolicy;
2123
import java.lang.annotation.Target;
@@ -32,7 +34,8 @@
3234
*/
3335
@Documented
3436
@Retention(RetentionPolicy.RUNTIME)
35-
@Target({})
37+
@Target(ElementType.METHOD)
38+
@Repeatable(Results.class)
3639
public @interface Result {
3740
/**
3841
* Returns whether id column or not.

src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,12 @@ private void parseCacheRef() {
229229
private String parseResultMap(Method method) {
230230
Class<?> returnType = getReturnType(method);
231231
ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
232+
Arg arg = method.getAnnotation(Arg.class);
232233
Results results = method.getAnnotation(Results.class);
234+
Result result = method.getAnnotation(Result.class);
233235
TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
234236
String resultMapId = generateResultMapName(method);
235-
applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);
237+
applyResultMap(resultMapId, returnType, argsIf(args, arg, method), resultsIf(results, result, method), typeDiscriminator);
236238
return resultMapId;
237239
}
238240

@@ -626,11 +628,23 @@ private String nullOrEmpty(String value) {
626628
return value == null || value.trim().length() == 0 ? null : value;
627629
}
628630

629-
private Result[] resultsIf(Results results) {
631+
private Result[] resultsIf(Results results, Result result, Method method) {
632+
if (results != null && result != null) {
633+
throw new BuilderException("Cannot use both @Results and @Result annotations with together at '" + method + "'.");
634+
}
635+
if (result != null) {
636+
return new Result[]{result};
637+
}
630638
return results == null ? new Result[0] : results.value();
631639
}
632640

633-
private Arg[] argsIf(ConstructorArgs args) {
641+
private Arg[] argsIf(ConstructorArgs args, Arg arg, Method method) {
642+
if (args != null && arg != null) {
643+
throw new BuilderException("Cannot use both @ConstructorArgs and @Arg annotations with together at '" + method + "'.");
644+
}
645+
if (arg != null) {
646+
return new Arg[]{arg};
647+
}
634648
return args == null ? new Arg[0] : args.value();
635649
}
636650

src/test/java/org/apache/ibatis/binding/BindingTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import net.sf.cglib.proxy.Factory;
3737

3838
import org.apache.ibatis.BaseDataTest;
39+
import org.apache.ibatis.builder.BuilderException;
3940
import org.apache.ibatis.cursor.Cursor;
4041
import org.apache.ibatis.domain.blog.Author;
4142
import org.apache.ibatis.domain.blog.Blog;
@@ -688,4 +689,76 @@ void registeredMappers() {
688689
assertTrue(mapperClasses.contains(BoundAuthorMapper.class));
689690
}
690691

692+
@Test
693+
void shouldMapPropertiesUsingRepeatableAnnotation() {
694+
try (SqlSession session = sqlSessionFactory.openSession()) {
695+
BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
696+
Author author = new Author(-1, "cbegin", "******", "[email protected]", "N/A", Section.NEWS);
697+
mapper.insertAuthor(author);
698+
Author author2 = mapper.selectAuthorMapToPropertiesUsingRepeatable(author.getId());
699+
assertNotNull(author2);
700+
assertEquals(author.getId(), author2.getId());
701+
assertEquals(author.getUsername(), author2.getUsername());
702+
assertEquals(author.getPassword(), author2.getPassword());
703+
assertEquals(author.getBio(), author2.getBio());
704+
assertEquals(author.getEmail(), author2.getEmail());
705+
session.rollback();
706+
}
707+
}
708+
709+
@Test
710+
void shouldMapConstructorUsingRepeatableAnnotation() {
711+
try (SqlSession session = sqlSessionFactory.openSession()) {
712+
BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
713+
Author author = new Author(-1, "cbegin", "******", "[email protected]", "N/A", Section.NEWS);
714+
mapper.insertAuthor(author);
715+
Author author2 = mapper.selectAuthorMapToConstructorUsingRepeatable(author.getId());
716+
assertNotNull(author2);
717+
assertEquals(author.getId(), author2.getId());
718+
assertEquals(author.getUsername(), author2.getUsername());
719+
assertEquals(author.getPassword(), author2.getPassword());
720+
assertEquals(author.getBio(), author2.getBio());
721+
assertEquals(author.getEmail(), author2.getEmail());
722+
assertEquals(author.getFavouriteSection(), author2.getFavouriteSection());
723+
session.rollback();
724+
}
725+
}
726+
727+
@Test
728+
void shouldMapUsingSingleRepeatableAnnotation() {
729+
try (SqlSession session = sqlSessionFactory.openSession()) {
730+
BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
731+
Author author = new Author(-1, "cbegin", "******", "[email protected]", "N/A", Section.NEWS);
732+
mapper.insertAuthor(author);
733+
Author author2 = mapper.selectAuthorUsingSingleRepeatable(author.getId());
734+
assertNotNull(author2);
735+
assertEquals(author.getId(), author2.getId());
736+
assertEquals(author.getUsername(), author2.getUsername());
737+
assertNull(author2.getPassword());
738+
assertNull(author2.getBio());
739+
assertNull(author2.getEmail());
740+
assertNull(author2.getFavouriteSection());
741+
session.rollback();
742+
}
743+
}
744+
745+
@Test
746+
void shouldErrorWhenSpecifyBothArgAndConstructorArgs() {
747+
try (SqlSession session = sqlSessionFactory.openSession()) {
748+
BuilderException exception = Assertions.assertThrows(BuilderException.class,()
749+
-> session.getConfiguration().addMapper(WrongArgAndConstructorArgsMapper.class));
750+
Assertions.assertEquals("Cannot use both @ConstructorArgs and @Arg annotations with together at 'public abstract org.apache.ibatis.domain.blog.Author org.apache.ibatis.binding.WrongArgAndConstructorArgsMapper.selectAuthor(int)'.", exception.getMessage());
751+
}
752+
}
753+
754+
@Test
755+
void shouldErrorWhenSpecifyBothResultAndResults() {
756+
try (SqlSession session = sqlSessionFactory.openSession()) {
757+
BuilderException exception = Assertions.assertThrows(BuilderException.class,()
758+
-> session.getConfiguration().addMapper(WrongResultAndResultsMapper.class));
759+
Assertions.assertEquals("Cannot use both @Results and @Result annotations with together at 'public abstract org.apache.ibatis.domain.blog.Author org.apache.ibatis.binding.WrongResultAndResultsMapper.selectAuthor(int)'.", exception.getMessage());
760+
}
761+
}
762+
691763
}
764+

src/test/java/org/apache/ibatis/binding/BoundAuthorMapper.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2018 the original author or authors.
2+
* Copyright 2009-2019 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.
@@ -68,6 +68,23 @@ public interface BoundAuthorMapper {
6868

6969
//======================================================
7070

71+
@Result(property = "id", column = "AUTHOR_ID", id = true)
72+
@Result(property = "username", column = "AUTHOR_USERNAME")
73+
@Result(property = "password", column = "AUTHOR_PASSWORD")
74+
@Result(property = "email", column = "AUTHOR_EMAIL")
75+
@Result(property = "bio", column = "AUTHOR_BIO")
76+
@Select({
77+
"SELECT ",
78+
" ID as AUTHOR_ID,",
79+
" USERNAME as AUTHOR_USERNAME,",
80+
" PASSWORD as AUTHOR_PASSWORD,",
81+
" EMAIL as AUTHOR_EMAIL,",
82+
" BIO as AUTHOR_BIO",
83+
"FROM AUTHOR WHERE ID = #{id}"})
84+
Author selectAuthorMapToPropertiesUsingRepeatable(int id);
85+
86+
//======================================================
87+
7188
@ConstructorArgs({
7289
@Arg(column = "AUTHOR_ID", javaType = Integer.class),
7390
@Arg(column = "AUTHOR_USERNAME", javaType = String.class),
@@ -89,6 +106,39 @@ public interface BoundAuthorMapper {
89106

90107
//======================================================
91108

109+
@Arg(column = "AUTHOR_ID", javaType = Integer.class, id = true)
110+
@Arg(column = "AUTHOR_USERNAME", javaType = String.class)
111+
@Arg(column = "AUTHOR_PASSWORD", javaType = String.class)
112+
@Arg(column = "AUTHOR_EMAIL", javaType = String.class)
113+
@Arg(column = "AUTHOR_BIO", javaType = String.class)
114+
@Arg(column = "AUTHOR_SECTION", javaType = Section.class)
115+
@Select({
116+
"SELECT ",
117+
" ID as AUTHOR_ID,",
118+
" USERNAME as AUTHOR_USERNAME,",
119+
" PASSWORD as AUTHOR_PASSWORD,",
120+
" EMAIL as AUTHOR_EMAIL,",
121+
" BIO as AUTHOR_BIO," +
122+
" FAVOURITE_SECTION as AUTHOR_SECTION",
123+
"FROM AUTHOR WHERE ID = #{id}"})
124+
Author selectAuthorMapToConstructorUsingRepeatable(int id);
125+
126+
//======================================================
127+
128+
@Arg(column = "AUTHOR_ID", javaType = int.class)
129+
@Result(property = "username", column = "AUTHOR_USERNAME")
130+
@Select({
131+
"SELECT ",
132+
" ID as AUTHOR_ID,",
133+
" USERNAME as AUTHOR_USERNAME,",
134+
" PASSWORD as AUTHOR_PASSWORD,",
135+
" EMAIL as AUTHOR_EMAIL,",
136+
" BIO as AUTHOR_BIO",
137+
"FROM AUTHOR WHERE ID = #{id}"})
138+
Author selectAuthorUsingSingleRepeatable(int id);
139+
140+
//======================================================
141+
92142
List<Post> findThreeSpecificPosts(@Param("one") int one,
93143
RowBounds rowBounds,
94144
@Param("two") int two,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright 2009-2019 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.binding;
17+
18+
import org.apache.ibatis.annotations.Arg;
19+
import org.apache.ibatis.annotations.ConstructorArgs;
20+
import org.apache.ibatis.annotations.Select;
21+
import org.apache.ibatis.domain.blog.Author;
22+
23+
public interface WrongArgAndConstructorArgsMapper {
24+
25+
@ConstructorArgs(@Arg(column = "AUTHOR_ID", javaType = int.class))
26+
@Arg(column = "AUTHOR_ID", javaType = int.class)
27+
@Select({
28+
"SELECT ",
29+
" ID as AUTHOR_ID",
30+
"FROM AUTHOR WHERE ID = #{id}"})
31+
Author selectAuthor(int id);
32+
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright 2009-2019 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.binding;
17+
18+
import org.apache.ibatis.annotations.Result;
19+
import org.apache.ibatis.annotations.Results;
20+
import org.apache.ibatis.annotations.Select;
21+
import org.apache.ibatis.domain.blog.Author;
22+
23+
public interface WrongResultAndResultsMapper {
24+
25+
@Results(@Result(property = "id", column = "AUTHOR_ID"))
26+
@Result(property = "id", column = "AUTHOR_ID")
27+
@Select({
28+
"SELECT ",
29+
" ID as AUTHOR_ID",
30+
"FROM AUTHOR WHERE ID = #{id}"})
31+
Author selectAuthor(int id);
32+
33+
}

0 commit comments

Comments
 (0)