Skip to content

Commit 706574c

Browse files
authored
Merge pull request #35014 from njr-11/multiple-QueryOptions-on-JakartaQuery
JakartaQuery method with multiple QueryOptions
2 parents 3119a7f + 967e5a8 commit 706574c

5 files changed

Lines changed: 159 additions & 8 deletions

File tree

dev/io.openliberty.data.internal_fat_1_1/fat/src/test/jakarta/data/v1_1/Data_1_1_HibernateTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public class Data_1_1_HibernateTest extends FATServletClient {
4646
new String[] {
4747
"CWWKD1054E.*findByIsControlTrueAndNumericValueBetween",
4848
"CWWKD1091E.*countBySurgePriceGreaterThanEqual",
49+
"DSRA0302E.*XA_RBTIMEOUT", // query timeout
50+
"DSRA0304E.*", // query timeout
51+
"J2CA0027E.*" // query timeout
4952
};
5053

5154
@ClassRule

dev/io.openliberty.data.internal_fat_1_1/fat/src/test/jakarta/data/v1_1/Data_1_1_Test.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public class Data_1_1_Test extends FATServletClient {
4444
new String[] {
4545
"CWWKD1054E.*findByIsControlTrueAndNumericValueBetween",
4646
"CWWKD1091E.*countBySurgePriceGreaterThanEqual",
47+
"DSRA0302E.*XA_RBTIMEOUT", // query timeout
48+
"DSRA0304E.*", // query timeout
49+
"J2CA0027E.*" // query timeout
4750
};
4851

4952
@ClassRule
@@ -67,7 +70,6 @@ public static void setUp() throws Exception {
6770

6871
WebArchive war = ShrinkHelper
6972
.buildDefaultApp("Data_1_1_App",
70-
"test.jakarta.data.v1_1.eclipselink",
7173
"test.jakarta.data.v1_1.web");
7274
ShrinkHelper.exportAppToServer(server, war);
7375
server.startServer();

dev/io.openliberty.data.internal_fat_1_1/test-applications/Data_1_1_App/src/test/jakarta/data/v1_1/web/Data_1_1_Servlet.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import java.util.ArrayList;
2525
import java.util.HashSet;
2626
import java.util.List;
27+
import java.util.Optional;
2728
import java.util.Set;
2829
import java.util.concurrent.CompletableFuture;
2930
import java.util.concurrent.CompletionException;
31+
import java.util.concurrent.CountDownLatch;
3032
import java.util.concurrent.TimeUnit;
3133
import java.util.stream.Collectors;
3234

@@ -41,6 +43,7 @@
4143
import jakarta.data.constraint.Like;
4244
import jakarta.data.constraint.NotBetween;
4345
import jakarta.data.constraint.NotNull;
46+
import jakarta.data.exceptions.DataException;
4447
import jakarta.data.expression.TextExpression;
4548
import jakarta.data.page.CursoredPage;
4649
import jakarta.data.page.Page;
@@ -58,6 +61,7 @@
5861

5962
import org.junit.Test;
6063

64+
import componenttest.annotation.AllowedFFDC;
6165
import componenttest.app.FATServlet;
6266
import test.jakarta.data.v1_1.web.Fraction.Decimal;
6367

@@ -102,6 +106,16 @@ public void init(ServletConfig config) throws ServletException {
102106
fractions.supply(fractionsToAdd);
103107
}
104108

109+
/**
110+
* Indicates if testing with the Derby database.
111+
*
112+
* @return true if testing with the Derby database.
113+
*/
114+
static final boolean isDerby() {
115+
String jdbcJarName = System.getenv().getOrDefault("DB_DRIVER", "UNKNOWN");
116+
return jdbcJarName.startsWith("derby");
117+
}
118+
105119
/**
106120
* Indicates if testing with the Hibernate Persistence provider
107121
* rather than EclipseLink.
@@ -1040,6 +1054,82 @@ public void testLikeConstraintWithPatternAndLimit() {
10401054
Limit.of(4)));
10411055
}
10421056

1057+
/**
1058+
* Use the QueryOptions annotation to establish pessimistic locking and
1059+
* a query timeout on a repository method annotated JakartaQuery.
1060+
*/
1061+
@AllowedFFDC("javax.transaction.xa.XAException") // due to query timeout
1062+
@Test
1063+
public void testLockModeAndQueryTimeoutAsQueryOptions() throws Exception {
1064+
// Populate with 18/23.
1065+
// Ensure deletion in the finally block.
1066+
fractions.supply(List.of(Fraction.of(18, 23)));
1067+
1068+
CountDownLatch locked = new CountDownLatch(1);
1069+
CountDownLatch blocked = new CountDownLatch(1);
1070+
CompletableFuture<Fraction> lockDB = CompletableFuture.supplyAsync(() -> {
1071+
try {
1072+
tx.setTransactionTimeout((int) TIMEOUT_S * 3);
1073+
tx.begin();
1074+
Optional<Fraction> found = //
1075+
fractions.withWriteLock("Eighteen Twenty-thirds");
1076+
System.out.println("Obtained lock on 18/23");
1077+
locked.countDown();
1078+
blocked.await(TIMEOUT_S * 2, TimeUnit.SECONDS);
1079+
System.out.println("Release lock on 18/23");
1080+
tx.commit();
1081+
return found.orElseThrow();
1082+
} catch (RuntimeException x) {
1083+
throw x;
1084+
} catch (Exception x) {
1085+
throw new CompletionException(x);
1086+
}
1087+
});
1088+
try {
1089+
assertEquals(true, locked.await(TIMEOUT_S, TimeUnit.SECONDS));
1090+
// Derby ignores query timeout and the lock timeout ends up applying
1091+
// instead. Work around this by also setting the lock timeout when
1092+
// running on Derby:
1093+
if (isDerby())
1094+
if (isHibernatePersistence())
1095+
statefulFractions.setLockTimeout(5);
1096+
else
1097+
fractions.setLockTimeout(5);
1098+
tx.begin();
1099+
try {
1100+
Optional<Fraction> found = //
1101+
fractions.withWriteLock("Eighteen Twenty-thirds");
1102+
fail("Query timeout on QueryOptions should have caused the" +
1103+
" query to time out. Instead found " + found);
1104+
} catch (DataException x) {
1105+
// expected for query timeout
1106+
} finally {
1107+
tx.rollback();
1108+
}
1109+
1110+
blocked.countDown();
1111+
tx.begin();
1112+
Fraction f18_23 = fractions.withWriteLock("Eighteen Twenty-thirds")
1113+
.orElseThrow();
1114+
assertEquals(0.7826,
1115+
f18_23.decimal.value(),
1116+
0.0001);
1117+
tx.commit();
1118+
} finally {
1119+
locked.countDown();
1120+
blocked.countDown();
1121+
// Ensure no fractions with denominator of 23 or more are left around
1122+
fractions.discard(AtLeast.min(23),
1123+
AtMost.max(Integer.MAX_VALUE),
1124+
Restrict.unrestricted());
1125+
if (isDerby())
1126+
if (isHibernatePersistence())
1127+
statefulFractions.setLockTimeout(60);
1128+
else
1129+
fractions.setLockTimeout(60);
1130+
}
1131+
}
1132+
10431133
/**
10441134
* Supply a LOWER expression to a repository method.
10451135
*/

dev/io.openliberty.data.internal_fat_1_1/test-applications/Data_1_1_App/src/test/jakarta/data/v1_1/web/Fractions.java

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
*******************************************************************************/
1313
package test.jakarta.data.v1_1.web;
1414

15+
import java.sql.CallableStatement;
16+
import java.sql.Connection;
17+
import java.sql.SQLException;
1518
import java.util.Collection;
1619
import java.util.List;
1720
import java.util.Optional;
@@ -46,6 +49,7 @@
4649
import jakarta.data.repository.Repository;
4750
import jakarta.data.repository.Select;
4851
import jakarta.data.restrict.Restriction;
52+
import jakarta.persistence.LockModeType;
4953

5054
/**
5155
* Repository for the Fraction entity
@@ -60,6 +64,8 @@ List<Integer> atMost10Numerators(int denominator,
6064
Restriction<Fraction> filter,
6165
Order<Fraction> sortBy);
6266

67+
Connection connect();
68+
6369
Long count(Restriction<Fraction> filter);
6470

6571
long countByDenominatorBetween(int min,
@@ -105,6 +111,13 @@ List<String> named(@By(_Fraction.NAME) Like pattern,
105111
Order<Fraction> order,
106112
Limit limit);
107113

114+
@Find
115+
@OrderBy(_Fraction.DENOMINATOR)
116+
CursoredPage<Fraction> namedLike //
117+
(@By(_Fraction.NAME) @Is(Like.class) String pattern,
118+
Order<Fraction> additionalSorting,
119+
PageRequest pageReq);
120+
108121
@Find
109122
@QueryOptions(entityGraph = "EagerlyLoadRoundedValues")
110123
Optional<Fraction> of(int numerator, int denominator);
@@ -117,6 +130,23 @@ List<String> named(@By(_Fraction.NAME) Like pattern,
117130
List<Fraction> remove(Like name,
118131
Restriction<Fraction> filter);
119132

133+
/**
134+
* This is a workaround for Derby, which ignores query timeout
135+
* and eventually the lock timeout (default 60s) applies instead.
136+
* Tests can use this method to set the lock timeout to the desired
137+
* query timeout value to make a query that involves a lock appear
138+
* to time out as expected if the query timeout were honored.
139+
*/
140+
default void setLockTimeout(int seconds) throws SQLException {
141+
String sql = "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, ?)";
142+
try (Connection con = connect()) {
143+
CallableStatement cs = con.prepareCall(sql);
144+
cs.setString(1, "derby.locks.waitTimeout");
145+
cs.setInt(2, seconds);
146+
cs.execute();
147+
}
148+
}
149+
120150
@Query("SELECT NEW test.jakarta.data.v1_1.web.Ratio(" +
121151
"\t\tnumerator, denominator - numerator)" +
122152
"\tWHERE numerator=?1 AND denominator=?2")
@@ -134,13 +164,6 @@ List<Fraction> squareRootBetween(double min,
134164
@Query("SELECT numerator, denominator - numerator")
135165
Stream<Ratio> streamOfRatios();
136166

137-
@Find
138-
@OrderBy(_Fraction.DENOMINATOR)
139-
CursoredPage<Fraction> namedLike //
140-
(@By(_Fraction.NAME) @Is(Like.class) String pattern,
141-
Order<Fraction> additionalSorting,
142-
PageRequest pageReq);
143-
144167
@Insert
145168
void supply(Collection<Fraction> list);
146169

@@ -176,4 +199,10 @@ List<Fraction> squareRootBetween(double min,
176199
(@By(_Fraction.NUMERATOR) In<Integer> numerators,
177200
@Is int denominator,
178201
Sort<Fraction> sort);
202+
203+
@JakartaQuery("WHERE name = :name")
204+
@QueryOptions(lockMode = LockModeType.PESSIMISTIC_WRITE,
205+
timeout = 10000) // query timeout = 10 seconds
206+
Optional<Fraction> withWriteLock(String name);
207+
179208
}

dev/io.openliberty.data.internal_fat_1_1/test-applications/Data_1_1_App/src/test/jakarta/data/v1_1/web/StatefulFractions.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
*******************************************************************************/
1313
package test.jakarta.data.v1_1.web;
1414

15+
import java.sql.CallableStatement;
16+
import java.sql.Connection;
17+
import java.sql.SQLException;
1518
import java.util.Optional;
1619

1720
import jakarta.data.constraint.EqualTo;
@@ -21,6 +24,7 @@
2124
import jakarta.data.repository.Repository;
2225
import jakarta.data.repository.stateful.Detach;
2326
import jakarta.persistence.EntityManager;
27+
import jakarta.transaction.Transactional;
2428

2529
/**
2630
* Stateful repository for the Fraction entity
@@ -41,4 +45,27 @@ default void flush() {
4145
}
4246

4347
EntityManager manager();
48+
49+
/**
50+
* This is a workaround for Derby, which ignores query timeout
51+
* and eventually the lock timeout (default 60s) applies instead.
52+
* Tests can use this method to set the lock timeout to the desired
53+
* query timeout value to make a query that involves a lock appear
54+
* to time out as expected if the query timeout were honored.
55+
*/
56+
@Transactional
57+
default void setLockTimeout(int seconds) throws SQLException {
58+
String sql = "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, ?)";
59+
EntityManager em = manager();
60+
em.runWithConnection((Connection con) -> {
61+
try {
62+
CallableStatement cs = con.prepareCall(sql);
63+
cs.setString(1, "derby.locks.waitTimeout");
64+
cs.setInt(2, seconds);
65+
cs.execute();
66+
} catch (SQLException x) {
67+
throw new RuntimeException(x);
68+
}
69+
});
70+
}
4471
}

0 commit comments

Comments
 (0)