Skip to content

Commit 75202a0

Browse files
committed
Merge pull request #160 from jeffgbutler/issue_154
Changes for #154 - Enhanced Support for Optimistic Locking
2 parents 8f4a619 + b83de26 commit 75202a0

File tree

8 files changed

+373
-13
lines changed

8 files changed

+373
-13
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
String keyProperty();
3131

32+
String keyColumn() default "";
33+
3234
boolean before();
3335

3436
Class<?> resultType();

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ void parseStatement(Method method) {
257257
KeyGenerator keyGenerator;
258258
String keyProperty = "id";
259259
String keyColumn = null;
260-
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
260+
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
261261
// first check for SelectKey annotation - that overrides everything else
262262
SelectKey selectKey = method.getAnnotation(SelectKey.class);
263263
if (selectKey != null) {
@@ -564,6 +564,7 @@ private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, St
564564
Class<?> resultTypeClass = selectKeyAnnotation.resultType();
565565
StatementType statementType = selectKeyAnnotation.statementType();
566566
String keyProperty = selectKeyAnnotation.keyProperty();
567+
String keyColumn = selectKeyAnnotation.keyColumn();
567568
boolean executeBefore = selectKeyAnnotation.before();
568569

569570
// defaults
@@ -581,7 +582,7 @@ private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, St
581582

582583
assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
583584
flushCache, useCache, false,
584-
keyGenerator, keyProperty, null, null, languageDriver, null);
585+
keyGenerator, keyProperty, keyColumn, null, languageDriver, null);
585586

586587
id = assistant.applyCurrentNamespace(id, false);
587588

src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> paramete
131131
Class<?> resultTypeClass = resolveClass(resultType);
132132
StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
133133
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
134+
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
134135
boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
135136

136137
//defaults
@@ -150,7 +151,7 @@ private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> paramete
150151
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
151152
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
152153
resultSetTypeEnum, flushCache, useCache, resultOrdered,
153-
keyGenerator, keyProperty, null, databaseId, langDriver, null);
154+
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
154155

155156
id = builderAssistant.applyCurrentNamespace(id, false);
156157

src/main/java/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,18 +203,22 @@ lang CDATA #IMPLIED
203203
resultType CDATA #IMPLIED
204204
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
205205
keyProperty CDATA #IMPLIED
206+
keyColumn CDATA #IMPLIED
206207
order (BEFORE|AFTER) #IMPLIED
207208
databaseId CDATA #IMPLIED
208209
>
209210

210-
<!ELEMENT update (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
211+
<!ELEMENT update (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
211212
<!ATTLIST update
212213
id CDATA #REQUIRED
213214
parameterMap CDATA #IMPLIED
214215
parameterType CDATA #IMPLIED
215216
timeout CDATA #IMPLIED
216217
flushCache (true|false) #IMPLIED
217218
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
219+
keyProperty CDATA #IMPLIED
220+
useGeneratedKeys (true|false) #IMPLIED
221+
keyColumn CDATA #IMPLIED
218222
databaseId CDATA #IMPLIED
219223
lang CDATA #IMPLIED
220224
>

src/main/java/org/apache/ibatis/executor/keygen/SelectKeyGenerator.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ public void processAfter(Executor executor, MappedStatement ms, Statement stmt,
5252
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
5353
try {
5454
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
55-
String keyProperty = keyStatement.getKeyProperties()[0]; // just one key property is supported
55+
String[] keyProperties = keyStatement.getKeyProperties();
5656
final Configuration configuration = ms.getConfiguration();
5757
final MetaObject metaParam = configuration.newMetaObject(parameter);
58-
if (keyProperty != null && metaParam.hasSetter(keyProperty)) {
58+
if (keyProperties != null) {
5959
// Do not close keyExecutor.
6060
// The transaction will be closed by parent executor.
6161
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
@@ -65,10 +65,19 @@ private void processGeneratedKeys(Executor executor, MappedStatement ms, Object
6565
} else if (values.size() > 1) {
6666
throw new ExecutorException("SelectKey returned more than one value.");
6767
} else {
68-
metaParam.setValue(keyProperty, values.get(0));
68+
MetaObject metaResult = configuration.newMetaObject(values.get(0));
69+
if (keyProperties.length == 1) {
70+
if (metaResult.hasGetter(keyProperties[0])) {
71+
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
72+
} else {
73+
// no getter for the property - maybe just a single value object
74+
// so try that
75+
setValue(metaParam, keyProperties[0], values.get(0));
76+
}
77+
} else {
78+
handleMultipleProperties(keyProperties, metaParam, metaResult);
79+
}
6980
}
70-
} else {
71-
throw new ExecutorException("No setter found for the keyProperty '" + keyProperty + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
7281
}
7382
}
7483
} catch (ExecutorException e) {
@@ -78,4 +87,30 @@ private void processGeneratedKeys(Executor executor, MappedStatement ms, Object
7887
}
7988
}
8089

90+
private void handleMultipleProperties(String[] keyProperties,
91+
MetaObject metaParam, MetaObject metaResult) {
92+
String[] keyColumns = keyStatement.getKeyColumns();
93+
94+
if (keyColumns == null || keyColumns.length == 0) {
95+
// no key columns specified, just use the property names
96+
for (int i = 0; i < keyProperties.length; i++) {
97+
setValue(metaParam, keyProperties[i], metaResult.getValue(keyProperties[i]));
98+
}
99+
} else {
100+
if (keyColumns.length != keyProperties.length) {
101+
throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties.");
102+
}
103+
for (int i = 0; i < keyProperties.length; i++) {
104+
setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
105+
}
106+
}
107+
}
108+
109+
private void setValue(MetaObject metaParam, String property, Object value) {
110+
if (metaParam.hasSetter(property)) {
111+
metaParam.setValue(property, value);
112+
} else {
113+
throw new ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
114+
}
115+
}
81116
}

src/test/java/org/apache/ibatis/submitted/selectkey/AnnotatedMapper.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
*/
1616
package org.apache.ibatis.submitted.selectkey;
1717

18+
import java.util.Map;
19+
1820
import org.apache.ibatis.annotations.Insert;
1921
import org.apache.ibatis.annotations.InsertProvider;
2022
import org.apache.ibatis.annotations.Options;
2123
import org.apache.ibatis.annotations.SelectKey;
24+
import org.apache.ibatis.annotations.Update;
2225

2326
public interface AnnotatedMapper {
2427

@@ -28,13 +31,45 @@ public interface AnnotatedMapper {
2831

2932
@Insert("insert into table2 (name) values(#{name})")
3033
@Options(useGeneratedKeys=true, keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED")
31-
int insertTable2WithOptions(Name name);
34+
int insertTable2WithGeneratedKey(Name name);
3235

36+
int insertTable2WithGeneratedKeyXml(Name name);
37+
38+
@Insert("insert into table2 (name) values(#{name})")
39+
@SelectKey(statement="select id, name_fred from table2 where id = identity()", keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED", before=false, resultType=Map.class)
40+
int insertTable2WithSelectKeyWithKeyMap(Name name);
41+
42+
int insertTable2WithSelectKeyWithKeyMapXml(Name name);
43+
44+
@Insert("insert into table2 (name) values(#{name})")
45+
@SelectKey(statement="select id as nameId, name_fred as generatedName from table2 where id = identity()", keyProperty="nameId,generatedName", before=false, resultType=Name.class)
46+
int insertTable2WithSelectKeyWithKeyObject(Name name);
47+
48+
int insertTable2WithSelectKeyWithKeyObjectXml(Name name);
49+
3350
@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
3451
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
3552
int insertTable3(Name name);
3653

3754
@InsertProvider(type=SqlProvider.class,method="insertTable3_2")
3855
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
3956
int insertTable3_2(Name name);
57+
58+
@Update("update table2 set name = #{name} where id = #{nameId}")
59+
@Options(useGeneratedKeys=true, keyProperty="generatedName")
60+
int updateTable2WithGeneratedKey(Name name);
61+
62+
int updateTable2WithGeneratedKeyXml(Name name);
63+
64+
@Update("update table2 set name = #{name} where id = #{nameId}")
65+
@SelectKey(statement="select name_fred from table2 where id = #{nameId}", keyProperty="generatedName", keyColumn="NAME_FRED", before=false, resultType=String.class)
66+
int updateTable2WithSelectKeyWithKeyMap(Name name);
67+
68+
int updateTable2WithSelectKeyWithKeyMapXml(Name name);
69+
70+
@Update("update table2 set name = #{name} where id = #{nameId}")
71+
@SelectKey(statement="select name_fred as generatedName from table2 where id = #{nameId}", keyProperty="generatedName", before=false, resultType=Name.class)
72+
int updateTable2WithSelectKeyWithKeyObject(Name name);
73+
74+
int updateTable2WithSelectKeyWithKeyObjectXml(Name name);
4075
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2009-2012 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 mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
20+
<mapper namespace="org.apache.ibatis.submitted.selectkey.AnnotatedMapper">
21+
<insert id="insertTable2WithSelectKeyWithKeyMapXml">
22+
<selectKey keyProperty="nameId,generatedName" keyColumn="ID,NAME_FRED" order="AFTER" resultType="java.util.Map">
23+
select id, name_fred from table2 where id = identity()
24+
</selectKey>
25+
insert into table2 (name) values(#{name})
26+
</insert>
27+
28+
<insert id="insertTable2WithSelectKeyWithKeyObjectXml">
29+
<selectKey keyProperty="nameId,generatedName" order="AFTER" resultType="org.apache.ibatis.submitted.selectkey.Name">
30+
select id as nameId, name_fred as generatedName from table2 where id = identity()
31+
</selectKey>
32+
insert into table2 (name) values(#{name})
33+
</insert>
34+
35+
<insert id="insertTable2WithGeneratedKeyXml" useGeneratedKeys="true" keyProperty="nameId,generatedName" keyColumn="ID,NAME_FRED">
36+
insert into table2 (name) values(#{name})
37+
</insert>
38+
39+
<update id="updateTable2WithSelectKeyWithKeyMapXml">
40+
<selectKey keyProperty="generatedName" keyColumn="NAME_FRED" order="AFTER" resultType="java.lang.String">
41+
select name_fred from table2 where id = #{nameId}
42+
</selectKey>
43+
update table2 set name = #{name} where id = #{nameId}
44+
</update>
45+
46+
<update id="updateTable2WithSelectKeyWithKeyObjectXml">
47+
<selectKey keyProperty="generatedName" order="AFTER" resultType="org.apache.ibatis.submitted.selectkey.Name">
48+
select name_fred as generatedName from table2 where id = #{nameId}
49+
</selectKey>
50+
update table2 set name = #{name} where id = #{nameId}
51+
</update>
52+
53+
<update id="updateTable2WithGeneratedKeyXml" useGeneratedKeys="true" keyProperty="generatedName" keyColumn="NAME_FRED">
54+
update table2 set name = #{name} where id = #{nameId}
55+
</update>
56+
</mapper>

0 commit comments

Comments
 (0)